Compare commits

..
Author SHA1 Message Date
XtraCube d7d9028433 Merge branch 'dev' into commission
# Conflicts:
#	MiraAPI/PluginLoading/MiraPluginManager.cs
2025-06-29 20:15:18 -04:00
XtraCube 24b52ededc Merge branch 'dev' into commission 2025-06-29 11:23:32 -04:00
XtraCube 5dbcc1435d Merge branch 'dev' into commission 2025-06-24 23:14:09 -04:00
XtraCube 79359ec67d Merge branch 'dev' into commission 2025-06-24 14:57:57 -04:00
XtraCube 0479ff96b1 Merge branch 'dev' into commission 2025-06-24 12:32:43 -04:00
XtraCube 4e314a0a7e Revert "fix role settings menu"
This reverts commit d9cc57d355.
2025-06-24 12:32:38 -04:00
XtraCube d9cc57d355 fix role settings menu 2025-06-24 11:31:54 -04:00
XtraCube 8fc5137e87 Merge branch 'dev' into commission
# Conflicts:
#	MiraAPI/Patches/Options/GameSettingMenuPatches.cs
#	MiraAPI/Patches/Options/LobbyViewPanePatches.cs
#	MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
#	MiraAPI/Utilities/Assets/MiraAssets.cs
#	MiraAPI/Utilities/Extensions.cs
2025-06-23 19:18:01 -04:00
XtraCube 16ff2f8492 Merge branch 'dev' into commission
# Conflicts:
#	MiraAPI/Utilities/Extensions.cs
2025-06-22 09:01:00 -04:00
XtraCube f943277e38 Merge branch 'presets' into commission 2025-06-20 08:43:21 -04:00
XtraCube dbb17a4cd7 Merge branch 'presets' into commission 2025-06-20 08:38:38 -04:00
XtraCube e83c5e504d Merge branch 'presets' into commission 2025-06-20 08:32:35 -04:00
XtraCube ed0b4ebbc4 Merge branch 'presets' into commission 2025-06-19 23:24:12 -04:00
XtraCube 9077a13b5d fix RPC warnings 2025-06-19 23:21:14 -04:00
XtraCube fb3eabfb29 Merge branch 'dev' into commission 2025-06-11 21:26:15 -04:00
XtraCube df87039884 Merge branch '2025.5.20' into commission
# Conflicts:
#	MiraAPI/Patches/Options/LobbyViewPanePatches.cs
2025-05-30 17:18:23 -04:00
XtraCube a51369a4a6 Fix ReportBodyEventPatch.cs 2025-05-29 22:04:05 -04:00
XtraCube 23fe7e6feb Merge branch '2025.5.20' into commission 2025-05-27 16:09:52 -04:00
XtraCube e2f387a3f6 Merge branch '2025.5.20' into commission 2025-05-23 10:32:45 -04:00
XtraCube 96f84b741c adjust modifier assignment for commission 2025-05-21 17:16:17 -04:00
XtraCube b8d24c28e1 Merge branch 'dev' into commission
# Conflicts:
#	MiraAPI/Patches/Options/LobbyViewPanePatches.cs
2025-05-18 15:56:09 -04:00
XtraCube 84e3f4085e fix role settings bug 2025-05-18 14:44:13 -04:00
XtraCube efb19cf46f fix broken settings menu 2025-05-18 14:35:53 -04:00
XtraCube 57a31b7761 fix changes in branch 2025-05-18 12:32:10 -04:00
XtraCube 0f5add5251 fix botched merge 2025-05-18 12:31:31 -04:00
XtraCube 7450c73243 Merge branch 'extensions' into commission
# Conflicts:
#	MiraAPI/GameOptions/IModdedOption.cs
#	MiraAPI/GameOptions/ModdedOptionsManager.cs
#	MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
#	MiraAPI/Patches/Options/GameSettingMenuPatches.cs
#	MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
#	MiraAPI/PluginLoading/MiraPluginManager.cs
#	MiraAPI/Utilities/Assets/MiraAssets.cs
#	MiraAPI/Utilities/Assets/SpriteTools.cs
2025-05-18 12:27:49 -04:00
XtraCube bc188191de Initial commit 2025-05-18 11:34:41 -04:00
170 changed files with 880 additions and 6748 deletions

No files matched your search

+2 -2
View File
@@ -2,12 +2,12 @@
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.1" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.10.14" PrivateAssets="all" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.4.15" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" UseSymboliclinksIfPossible="true" />
</Target>
+2 -2
View File
@@ -8,7 +8,7 @@
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.3.8</VersionPrefix>
<VersionPrefix>0.2.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>All Of Us, XtraCube, Angxl</Authors>
@@ -42,4 +42,4 @@
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)\stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
</Project>
@@ -1,19 +1,19 @@
using MiraAPI.Example.GameOver;
using MiraAPI.Example.Roles;
using MiraAPI.Example.Extension.GameOver;
using MiraAPI.Example.Extension.Roles;
using MiraAPI.GameEnd;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
namespace MiraAPI.Example.Extension.Buttons;
public class NeutralKillerButton : CustomActionButton
{
public override string Name => "Win Game";
public override float Cooldown => 0f;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override MiraKeybind Keybind => ExampleKeybinds.NeutralWinKeybind;
protected override void OnClick()
{
@@ -0,0 +1,31 @@
using System;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
using Reactor;
using Reactor.Networking;
using Reactor.Networking.Attributes;
namespace MiraAPI.Example.Extension;
[BepInAutoPlugin("mira.example.extension", "MiraExampleExtensionMod")]
[BepInProcess("Among Us.exe")]
[BepInDependency(ReactorPlugin.Id)]
[BepInDependency(MiraApiPlugin.Id)]
[BepInDependency(ExamplePlugin.Id)]
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class ExampleExtensionPlugin : BasePlugin, IMiraExtension
{
public Harmony Harmony { get; } = new(Id);
public override void Load()
{
Harmony.PatchAll();
}
public Type GetBasePluginType()
{
return typeof(ExamplePlugin);
}
}
@@ -1,9 +1,9 @@
using MiraAPI.Example.Roles;
using MiraAPI.Example.Extension.Roles;
using MiraAPI.GameEnd;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example.GameOver;
namespace MiraAPI.Example.Extension.GameOver;
public class NeutralKillerGameOver : CustomGameOver
{
@@ -0,0 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<Description>Example extension for Mira API</Description>
<Authors>Angxl, XtraCube</Authors>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
<ProjectReference Include="..\MiraAPI.Example\MiraAPI.Example.csproj" />
</ItemGroup>
<Import Project="..\AmongUs.props" />
</Project>
@@ -0,0 +1,16 @@
using MiraAPI.Example.Extension.Options;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Extension.Modifiers;
public class ExtensionModifier : GameModifier
{
public override string ModifierName => "Extension Modifier";
public override string GetDescription() => "This is from the extension mod!";
public override int GetAssignmentChance() => (int)OptionGroupSingleton<ExtensionModifierOptions>.Instance.ModifierChance;
public override int GetAmountPerGame() => (int)OptionGroupSingleton<ExtensionModifierOptions>.Instance.ModifierLimit;
}
@@ -0,0 +1,13 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
namespace MiraAPI.Example.Extension.Options;
public class ExtendedFreezerOptions : AbstractOptionGroup<FreezerRole>
{
public override string GroupName => "Extended Freezer Role Options";
[ModdedNumberOption("Freeze Uses", 1, 5)]
public float FreezeUses { get; set; } = 1;
}
@@ -0,0 +1,17 @@
using MiraAPI.Example.Extension.Modifiers;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
namespace MiraAPI.Example.Extension.Options;
public class ExtensionModifierOptions : AbstractOptionGroup<ExtensionModifier>
{
public override string GroupName => "Extension Modifier Options";
[ModdedNumberOption("Modifier Chance", 0, 100, 10, MiraNumberSuffixes.Percent)]
public float ModifierChance { get; set; } = 100f;
[ModdedNumberOption("Modifier Limit", 0, 10)]
public float ModifierLimit { get; set; } = 0;
}
@@ -0,0 +1,15 @@
using HarmonyLib;
using MiraAPI.Example.Buttons.Freezer;
using MiraAPI.Example.Extension.Options;
using MiraAPI.GameOptions;
namespace MiraAPI.Example.Extension.Patches;
[HarmonyPatch(typeof(FreezeButton), nameof(FreezeButton.MaxUses), MethodType.Getter)]
public static class FreezeButtonPatch
{
public static void Postfix(ref int __result)
{
__result = (int)OptionGroupSingleton<ExtendedFreezerOptions>.Instance.FreezeUses;
}
}
@@ -1,17 +1,20 @@
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Example.GameOver;
using MiraAPI.Example.Extension.GameOver;
using MiraAPI.GameEnd;
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Example.Roles;
namespace MiraAPI.Example.Extension.Roles;
public class NeutralKillerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Outcast Killer";
public string RoleDescription => "Outcast who can kill.";
public string RoleLongDescription => RoleDescription;
public string RoleLongDescription => this.RoleDescription;
public Color RoleColor => Color.magenta;
public ModdedRoleTeams Team => ModdedRoleTeams.Custom;
public CustomRoleConfiguration Configuration => new(this)
@@ -23,7 +26,6 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray);
[HideFromIl2Cpp]
public TeamIntroConfiguration? IntroConfiguration { get; } = new(
Color.gray,
"OUTCAST",
@@ -3,7 +3,6 @@ using MiraAPI.Example.Options.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Modifiers;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
@@ -16,10 +15,10 @@ public class FreezeButton : CustomActionButton<PlayerControl>
public override string Name => "Freeze";
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
public override bool PauseTimerInVent => true;
public override int MaxUses => 1;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override MiraKeybind? Keybind => MiraGlobalKeybinds.PrimaryAbility;
protected override void OnClick()
{
-2
View File
@@ -1,6 +1,5 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Modifiers;
using MiraAPI.Utilities.Assets;
using UnityEngine;
@@ -16,7 +15,6 @@ public class MeetingButton : CustomActionButton
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override MiraKeybind? Keybind => MiraGlobalKeybinds.ModifierPrimaryAbility;
public override bool Enabled(RoleBehaviour? role)
{
@@ -3,7 +3,6 @@ using MiraAPI.Example.Options.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Keybinds;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
@@ -22,7 +21,6 @@ public class TeleportButton : CustomActionButton
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
public static bool IsZoom { get; private set; }
public override MiraKeybind? Keybind => MiraGlobalKeybinds.PrimaryAbility;
public override bool Enabled(RoleBehaviour? role)
{
-35
View File
@@ -1,35 +0,0 @@
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
public class ThinkButton : CustomActionButton
{
public override string Name => "Think";
public override float Cooldown => 15f;
public override float EffectDuration => 10f;
public override int MaxUses => 1;
public override ButtonUsesMode UsesMode => ButtonUsesMode.PerRound;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
protected override void OnClick()
{
// This button does absolutely nothing besides changing its text
Button!.OverrideText("Thinking...");
}
public override void OnEffectEnd()
{
Button!.OverrideText("Think");
}
public override bool IsEffectCancellable()
{
return true;
}
public override bool Enabled(RoleBehaviour? role)
{
return role != null && role.IsImpostor;
}
}
+3 -19
View File
@@ -1,5 +1,4 @@
using System.Globalization;
using System.Linq;
using System.Linq;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Events.Vanilla.Gameplay;
@@ -18,7 +17,7 @@ public static class ExampleEventHandlers
{
public static void Initialize()
{
// You can register event handlers with the MiraEventManager class
// You can register event handlers with the MiraEventManager class.
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
{
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
@@ -38,21 +37,6 @@ public static class ExampleEventHandlers
MiraEventManager.UnregisterEventHandler(handle);
}
// If you want to check vanilla buttons, use something like this!
[RegisterEvent]
public static void VanillaButtonClickHandler(VanillaButtonClickEvent @event)
{
Logger<ExamplePlugin>.Info($"{@event.Button.buttonLabelText.text} is being used!");
if (@event.PlayerTarget != null)
{
Logger<ExamplePlugin>.Info($"{@event.PlayerTarget.Data.PlayerName} is being targeted!");
}
else if (@event.VentTarget != null)
{
Logger<ExamplePlugin>.Info($"Vent with id {@event.VentTarget.Id.ToString(CultureInfo.InvariantCulture)} is being targeted!");
}
}
// If you want to add extra votes to a player, do something like this.
[RegisterEvent]
public static void StartMeetingEvent(StartMeetingEvent _)
@@ -67,7 +51,7 @@ public static class ExampleEventHandlers
[RegisterEvent(15)]
public static void HandleVoteEvent(HandleVoteEvent @event)
{
if (@event.VoteData.Owner.Data.Role is not NeutralKillerRole) return;
if (@event.VoteData.Owner.Data.Role is not FreezerRole) return;
@event.VoteData.SetRemainingVotes(0);
-10
View File
@@ -1,10 +0,0 @@
using MiraAPI.Keybinds;
using Rewired;
namespace MiraAPI.Example;
[RegisterCustomKeybinds]
public static class ExampleKeybinds
{
public static MiraKeybind NeutralWinKeybind { get; } = new("Neutral Win", KeyboardKeyCode.C, [ModifierKey.Shift]);
}
-53
View File
@@ -1,53 +0,0 @@
using BepInEx.Configuration;
using MiraAPI.LocalSettings;
using MiraAPI.LocalSettings.Attributes;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example;
public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config)
{
public override string TabName => "Mira Example";
protected override bool ShouldCreateLabels => false;
public override LocalSettingTabAppearance TabAppearance => new()
{
ToggleActiveColor = Color.blue,
ToggleInactiveColor = Color.red,
};
[LocalSettingsButton]
public LocalSettingsButton ExampleButton { get; private set; } = new("Example Button", OnExampleButtonClick);
[LocalToggleSetting]
public ConfigEntry<bool> ExampleToggle { get; private set; } = config.Bind("General", "Example Bool", true);
[LocalToggleSetting]
public ConfigEntry<bool> ExampleToggle2 { get; private set; } = config.Bind("General", "Example Bool 2", false);
[LocalSliderSetting(suffixType: MiraNumberSuffixes.Percent, formatString: "0", roundValue: true)]
public ConfigEntry<float> ExampleSlider { get; private set; } = config.Bind("General", "Slider", 50f);
[LocalNumberSetting]
public ConfigEntry<float> ExampleNumber { get; private set; } = config.Bind("General", "Example Number", 4f);
[LocalNumberSetting(min: 0.1f, max: 2.5f, increment: 0.1f, formatString: "0.0", suffixType: MiraNumberSuffixes.Multiplier)]
public ConfigEntry<float> ExampleFloatNumber { get; private set; } = config.Bind("General", "Example Multplier", 1f);
[LocalEnumSetting]
public ConfigEntry<ExampleEnumSetting> ExampleEnum { get; private set; } = config.Bind("General", "Example Enum", ExampleEnumSetting.Fries);
private static void OnExampleButtonClick()
{
LocalSettingsTabSingleton<MiraApiSettings>.Instance.Open();
}
}
public enum ExampleEnumSetting
{
Cheeseburger,
Sandwich,
Fries,
ChickenNuggets,
}
+1 -2
View File
@@ -1,5 +1,4 @@
using System.Linq;
using BepInEx;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
@@ -11,7 +11,4 @@ public class FreezerRoleSettings : AbstractOptionGroup<FreezerRole>
[ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
public float FreezeDuration { get; set; } = 5;
[ModdedNumberOption("Freeze Uses", 1, 5)]
public float FreezeUses { get; set; } = 1;
}
+6
View File
@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiraAPI", "MiraAPI\MiraAPI.
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example", "MiraAPI.Example\MiraAPI.Example.csproj", "{19B76855-16CC-43CC-A0A6-5A7FFB43F077}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example.Extension", "MiraAPI.Example.Extension\MiraAPI.Example.Extension.csproj", "{526FF017-80E1-4094-804D-741AF35AA226}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -21,6 +23,10 @@ Global
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Debug|Any CPU.Build.0 = Debug|Any CPU
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.ActiveCfg = Release|Any CPU
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.Build.0 = Release|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.Build.0 = Debug|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.ActiveCfg = Release|Any CPU
{526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
+2 -2
View File
@@ -34,7 +34,7 @@ public static class MiraEventManager
}
catch (Exception ex)
{
Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
}
}
@@ -63,7 +63,7 @@ public static class MiraEventManager
}
catch (Exception ex)
{
Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
}
}
@@ -1,6 +1,4 @@
using MiraAPI.Networking;
namespace MiraAPI.Events.Vanilla.Gameplay;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked before a player is murdered. This event is cancelable.
@@ -17,24 +15,6 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
/// </summary>
public PlayerControl Target { get; }
/// <summary>
/// Gets whether the murder was meant to be done in a meeting via an enum.
/// </summary>
public MeetingCheck InMeeting { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The player that is killing the target.</param>
/// <param name="target">The player that is being killed.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target, MeetingCheck inMeeting)
{
Source = source;
Target = target;
InMeeting = inMeeting;
}
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
@@ -44,6 +24,5 @@ public sealed class BeforeMurderEvent : MiraCancelableEvent
{
Source = source;
Target = target;
InMeeting = MeetingCheck.Ignore;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event invoked on IntroCutscene.OnDestroy and ExileController.WrapUp to determine if it should run the round event or not.
/// </summary>
public class BeforeRoundStartEvent : MiraCancelableEvent
{
/// <summary>
/// Gets a value indicating whether the event was triggered by the IntroCutscene or ExileController.
/// </summary>
public bool TriggeredByIntro { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeRoundStartEvent"/> class.
/// </summary>
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
public BeforeRoundStartEvent(bool triggeredByIntro)
{
TriggeredByIntro = triggeredByIntro;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the player's role is shown on the intro cutscene. Non cancelable.
/// </summary>
public class IntroRoleRevealEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the IntroCutscene.
/// </summary>
public IntroCutscene IntroCutscene { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IntroRoleRevealEvent"/> class.
/// </summary>
/// <param name="introCutscene">The intro cutscene.</param>
public IntroRoleRevealEvent(IntroCutscene introCutscene)
{
IntroCutscene = introCutscene;
}
}
@@ -1,7 +1,7 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp if the event isn't cancelled.
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp.
/// </summary>
public class RoundStartEvent : MiraEvent
{
@@ -1,44 +0,0 @@
using MiraAPI.Hud;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
/// </summary>
/// <typeparam name="T">The Vanilla Button type.</typeparam>
public sealed class VanillaButtonCancelledEvent<T> : MiraEvent where T : AbilityButton
{
/// <summary>
/// Gets Vanilla Button whose click was cancelled.
/// </summary>
public T Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent{T}"/> class.
/// </summary>
/// <param name="button">The Mira Button whose click was cancelled.</param>
public VanillaButtonCancelledEvent(T button)
{
Button = button;
}
}
/// <summary>
/// Invoked when a Vanilla Button click is cancelled. Do not use for custom buttons.
/// </summary>
public sealed class VanillaButtonCancelledEvent : MiraEvent
{
/// <summary>
/// Gets Vanilla Button whose click was cancelled.
/// </summary>
public AbilityButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonCancelledEvent"/> class.
/// </summary>
/// <param name="button">The Vanilla Button whose click was cancelled.</param>
public VanillaButtonCancelledEvent(AbilityButton button)
{
Button = button;
}
}
@@ -1,65 +0,0 @@
using MiraAPI.Hud;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
/// </summary>
/// <typeparam name="T">The Vanilla Button type.</typeparam>
public sealed class VanillaButtonClickEvent<T> : MiraCancelableEvent where T : AbilityButton
{
/// <summary>
/// Gets Vanilla Button that was clicked.
/// </summary>
public T Button { get; }
/// <summary>
/// Gets the generic click event that is invoked before this button-specific event.
/// </summary>
public VanillaButtonClickEvent GenericClickEvent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent{T}"/> class.
/// </summary>
/// <param name="button">The Mira Button that was clicked.</param>
/// <param name="genericClickEvent">The generic click event invoked before button-specific events.</param>
public VanillaButtonClickEvent(T button, VanillaButtonClickEvent genericClickEvent)
{
GenericClickEvent = genericClickEvent;
Button = button;
}
}
/// <summary>
/// Button click event for Vanilla Buttons only. Do not use for custom buttons.
/// </summary>
public sealed class VanillaButtonClickEvent : MiraCancelableEvent
{
/// <summary>
/// Gets Vanilla Button that was clicked.
/// </summary>
public AbilityButton Button { get; }
/// <summary>
/// Gets the target player, if any, of the Vanilla Button that was clicked.
/// </summary>
public PlayerControl? PlayerTarget { get; }
/// <summary>
/// Gets the target vent, if any, of the Vanilla Button that was clicked.
/// </summary>
public Vent? VentTarget { get; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaButtonClickEvent"/> class.
/// </summary>
/// <param name="button">The Vanilla Button that was clicked.</param>
/// <param name="playerTarget">The Player target, if any.</param>
/// <param name="ventTarget">The Vent target, if any.</param>
public VanillaButtonClickEvent(AbilityButton button, PlayerControl? playerTarget = null, Vent? ventTarget = null)
{
Button = button;
PlayerTarget = playerTarget;
VentTarget = ventTarget;
}
}
@@ -1,30 +0,0 @@
using MiraAPI.Voting;
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Event that is invoked after the local player successfully votes a player or skips. This event is not cancelable.
/// </summary>
public class AfterVoteEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the voter's vote data.
/// </summary>
public PlayerVoteArea VoteArea { get; }
/// <summary>
/// Gets the player who voted.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="AfterVoteEvent"/> class.
/// </summary>
/// <param name="playerVoteArea">The player vote area that was voted on.</param>
/// <param name="voter">The player who voted.</param>
public AfterVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter)
{
VoteArea = playerVoteArea;
Player = voter;
}
}
@@ -1,30 +0,0 @@
using MiraAPI.Voting;
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Event that is invoked before the local player confirms a vote on a player or when skipping. This event is cancelable.
/// </summary>
public class BeforeVoteEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the instance of the voter's vote data.
/// </summary>
public PlayerVoteArea VoteArea { get; }
/// <summary>
/// Gets the player who voted.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeVoteEvent"/> class.
/// </summary>
/// <param name="playerVoteArea">The player vote area that was voted on.</param>
/// <param name="voter">The player who voted.</param>
public BeforeVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter)
{
VoteArea = playerVoteArea;
Player = voter;
}
}
@@ -1,44 +0,0 @@
using System;
using MiraAPI.Utilities;
using MiraAPI.Voting;
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
/// <summary>
/// Invoked before the dummy selects their vote. If canceled, the dummy won't vote.
/// </summary>
public class DummyVoteEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the dummy that needs to vote.
/// </summary>
public DummyBehaviour Dummy { get; }
/// <summary>
/// Gets the instance of the dummy's vote data.
/// </summary>
public PlayerVoteData VoteData { get; }
/// <summary>
/// Gets or sets a value indicating whether the dummy can vote to skip the meeting.
/// </summary>
public bool CanSkip { get; set; }
/// <summary>
/// Gets or sets a predicate for validating whether the dummy can vote on a player.
/// By default, excludes dead and disconnected players.
/// </summary>
public Predicate<PlayerControl> PlayerIsValid { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="DummyVoteEvent"/> class.
/// </summary>
/// <param name="dummy">The voting dummy.</param>
public DummyVoteEvent(DummyBehaviour dummy)
{
Dummy = dummy;
VoteData = dummy.myPlayer.GetVoteData();
CanSkip = true;
PlayerIsValid = x => !x.Data.IsDead && !x.Data.Disconnected;
}
}
@@ -1,35 +0,0 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// The event that is invoked when a player completes a task within Hide and Seek. Non-cancelable.
/// </summary>
public class CompleteHnsTaskEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the <see cref="PlayerControl"/>.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the instance of the <see cref="PlayerTask"/> that the player completed.
/// </summary>
public PlayerTask Task { get; }
/// <summary>
/// Gets the instance of the <see cref="HnsManager"/> attached to the game.
/// </summary>
public HideAndSeekManager HnsManager { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CompleteHnsTaskEvent"/> class.
/// </summary>
/// <param name="hnsManager">The Hide and Seek Manager currently active.</param>
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public CompleteHnsTaskEvent(HideAndSeekManager hnsManager, PlayerControl player, PlayerTask task)
{
HnsManager = hnsManager;
Player = player;
Task = task;
}
}
+2 -2
View File
@@ -30,13 +30,13 @@ public static class GameOverManager
if (gameOverType.IsAbstract)
{
Error("The type must not be abstract.");
Logger<MiraApiPlugin>.Error("The type must not be abstract.");
return false;
}
if (gameOverType.GetConstructor(Type.EmptyTypes) == null)
{
Error("The type must have a parameterless constructor.");
Logger<MiraApiPlugin>.Error("The type must have a parameterless constructor.");
return false;
}
+4 -4
View File
@@ -37,7 +37,7 @@ public static class CustomGameModeManager
return;
}
Error($"No gamemode with id {id} found!");
Logger<MiraApiPlugin>.Error($"No gamemode with id {id} found!");
}
/// <summary>
@@ -48,7 +48,7 @@ public static class CustomGameModeManager
{
if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType))
{
Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
Logger<MiraApiPlugin>.Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
return;
}
@@ -56,13 +56,13 @@ public static class CustomGameModeManager
if (modeObj is not CustomGameMode gameMode)
{
Error($"Failed to create instance of {gameModeType.Name}");
Logger<MiraApiPlugin>.Error($"Failed to create instance of {gameModeType.Name}");
return;
}
if (GameModes.Any(x => x.Key == gameMode.Id))
{
Error($"ID for gamemode {gameMode.Name} already exists!");
Logger<MiraApiPlugin>.Error($"ID for gamemode {gameMode.Name} already exists!");
return;
}
+1 -36
View File
@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using MiraAPI.Modifiers;
using UnityEngine;
namespace MiraAPI.GameOptions;
@@ -29,16 +28,11 @@ public abstract class AbstractOptionGroup
public virtual Type? OptionableType => null;
/// <summary>
/// Gets a value indicating whether the group should be shown in the modifiers menu. This is deprecated, please use ParentMenu!
/// Gets a value indicating whether the group should be shown in the modifiers menu.
/// </summary>
// TODO: make this not a boolean
public virtual bool ShowInModifiersMenu => false;
/// <summary>
/// Gets a value indicating which menu the group is in.
/// </summary>
public virtual MenuCategory ParentMenu => MenuCategory.Roles;
/// <summary>
/// Gets the function that determines whether the group should be visible or not.
/// </summary>
@@ -68,33 +62,4 @@ public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOp
{
/// <inheritdoc />
public override Type OptionableType => typeof(T);
/// <summary>
/// Gets a value indicating which menu the group is in.
/// </summary>
public override MenuCategory ParentMenu
{
get
{
var type = typeof(T);
if (type == typeof(BaseModifier))
{
return MenuCategory.Modifiers;
}
else if (type == typeof(RoleBehaviour))
{
return MenuCategory.Roles;
}
return MenuCategory.Game;
}
}
}
public enum MenuCategory
{
Game,
Roles,
Modifiers,
CustomOne,
CustomTwo
}
@@ -21,15 +21,7 @@ public class ModdedNumberOptionAttribute(
{
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
{
return new ModdedNumberOption(
Title,
(float)(value ?? min + increment),
min,
max,
increment,
suffixType,
formatString,
zeroInfinity);
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity);
}
/// <inheritdoc />
+2 -2
View File
@@ -17,9 +17,9 @@ public interface IModdedOption
uint Id { get; }
/// <summary>
/// Gets or sets the title of the option.
/// Gets the title of the option.
/// </summary>
string Title { get; set; }
string Title { get; }
/// <summary>
/// Gets the StringName for the option, used for localization.
@@ -1,40 +0,0 @@
using System;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.GameOptions.OptionTypes;
using Reactor.Utilities.Attributes;
using UnityEngine;
namespace MiraAPI.GameOptions;
/// <summary>
/// Component added onto options generated via MiraAPI. This allows developers to hook patches onto options in general, without using a list.
/// </summary>
[RegisterInIl2Cpp]
public class MiraNumberOptionComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
/// <summary>
/// Gets or sets the modded option associated with the object.
/// </summary>
[HideFromIl2Cpp]
public ModdedNumberOption NumberOption { get; set; }
/// <summary>
/// Gets or sets the default increment.
/// </summary>
public float DefaultIncrement { get; set; }
/// <summary>
/// Gets or sets a value indicating whether holding shift will split the increment in half.
/// </summary>
public bool ShiftIncrementToggle { get; set; }
/// <summary>
/// Gets or sets a value indicating what Zero displays.
/// </summary>
public string ZeroValue { get; set; } = "#";
/// <summary>
/// Gets or sets a value indicating what Negative One displays.
/// </summary>
public string NegativeValue { get; set; } = "#";
}
+6 -7
View File
@@ -24,7 +24,6 @@ public static class ModdedOptionsManager
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
internal static readonly Dictionary<OptionBehaviour, ModdedStringOption> CreatedStringOptions = [];
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
internal static readonly List<AbstractOptionGroup> Groups = [];
@@ -40,7 +39,7 @@ public static class ModdedOptionsManager
if (TypeToGroup.ContainsKey(type))
{
Error($"Group {type.Name} already exists.");
Logger<MiraApiPlugin>.Error($"Group {type.Name} already exists.");
return false;
}
@@ -61,13 +60,13 @@ public static class ModdedOptionsManager
{
if (!TypeToGroup.TryGetValue(type, out var group))
{
Error($"Failed to get group for {type.Name}");
Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
return;
}
if (property.GetValue(group) is not IModdedOption option)
{
Error($"Failed to get option for {property.Name}");
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
return;
}
@@ -82,13 +81,13 @@ public static class ModdedOptionsManager
{
if (OptionAttributes.ContainsKey(property))
{
Error($"Property {property.Name} already has an attribute registered.");
Logger<MiraApiPlugin>.Error($"Property {property.Name} already has an attribute registered.");
return;
}
if (!TypeToGroup.TryGetValue(type, out var group))
{
Error($"Failed to get group for {type.Name}");
Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
return;
}
@@ -96,7 +95,7 @@ public static class ModdedOptionsManager
if (option == null)
{
Error($"Failed to get option for {property.Name}");
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
return;
}
@@ -47,8 +47,6 @@ public class ModdedEnumOption : ModdedOption<int>
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.name =
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
@@ -140,8 +138,6 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.name =
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
@@ -32,82 +32,10 @@ public class ModdedNumberOption : ModdedOption<float>
/// </summary>
public MiraNumberSuffixes SuffixType { get; }
/// <summary>
/// Gets a value indicating what zero appears as.
/// </summary>
public string ZeroWordValue { get; }
/// <summary>
/// Gets a value indicating what a negative one appears as.
/// </summary>
public string NegativeWordValue { get; }
/// <summary>
/// Gets a value indicating whether zero is infinity.
/// </summary>
public bool ZeroInfinity => ZeroWordValue == "∞";
/// <summary>
/// Gets a value indicating whether holding shift will divide the increment.
/// </summary>
public bool ShiftIncrement { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as a float.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <param name="increment">The increment.</param>
/// <param name="suffixType">The suffix type.</param>
/// <param name="formatString">Optional format string for the option screen.</param>
/// <param name="zeroBehavior">Determines what is shown for zero. Options: ∞, #, or a word value (such as Off).</param>
/// <param name="negativeBehavior">Determines what is shown for negative 1. Options: ∞, #, or a word value (such as Off).</param>
/// <param name="halfIncrements">Whether increments can be split in half.</param>
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
public ModdedNumberOption(
string title,
float defaultValue,
float min,
float max,
float increment,
string zeroBehavior,
string negativeBehavior,
MiraNumberSuffixes suffixType,
string? formatString = null,
bool halfIncrements = false,
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
{
Min = min;
Max = max;
Increment = increment;
SuffixType = suffixType;
ZeroWordValue = zeroBehavior;
NegativeWordValue = negativeBehavior;
ShiftIncrement = halfIncrements;
Value = Mathf.Clamp(defaultValue, min, max);
Data = ScriptableObject.CreateInstance<FloatGameSetting>();
var data = (FloatGameSetting)Data;
data.Type = global::OptionTypes.Float;
data.Title = StringName;
data.Value = Value;
data.Increment = Increment;
data.ValidRange = new FloatRange(Min, Max);
data.FormatString = formatString ??
(defaultValue.IsInteger() &&
Increment.IsInteger() &&
Value.IsInteger() &&
Min.IsInteger() &&
Max.IsInteger() ? "0" : "0.0");
data.ZeroIsInfinity = ZeroInfinity;
data.SuffixType = (NumberSuffixes)SuffixType;
data.OptionName = FloatOptionNames.Invalid;
}
public bool ZeroInfinity { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
@@ -136,9 +64,7 @@ public class ModdedNumberOption : ModdedOption<float>
Max = max;
Increment = increment;
SuffixType = suffixType;
ZeroWordValue = zeroInfinity ? "∞" : "#";
NegativeWordValue = "#";
ShiftIncrement = increment != 1f;
ZeroInfinity = zeroInfinity;
Value = Mathf.Clamp(defaultValue, min, max);
@@ -171,14 +97,6 @@ public class ModdedNumberOption : ModdedOption<float>
Transform container)
{
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
numberOption.name =
$"{ParentMod!.OptionsTitleText}.NumberOption.{TranslationController.Instance.GetString(StringName)}";
var optionComponent = numberOption.gameObject.AddComponent<MiraNumberOptionComponent>();
optionComponent.NumberOption = this;
optionComponent.DefaultIncrement = Increment;
optionComponent.ShiftIncrementToggle = ShiftIncrement;
optionComponent.NegativeValue = NegativeWordValue;
optionComponent.ZeroValue = ZeroWordValue;
numberOption.SetUpFromData(Data, 20);
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
@@ -21,7 +21,7 @@ public abstract class ModdedOption<T> : IModdedOption
public uint Id { get; }
/// <inheritdoc />
public string Title { get; set; }
public string Title { get; }
/// <inheritdoc />
public StringNames StringName { get; }
@@ -125,7 +125,7 @@ public abstract class ModdedOption<T> : IModdedOption
{
if (ConfigDefinition is null)
{
Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
Logger<MiraApiPlugin>.Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
return;
}
Bind(presetConfig);
@@ -147,7 +147,7 @@ public abstract class ModdedOption<T> : IModdedOption
}
else
{
Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
Logger<MiraApiPlugin>.Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
}
}
@@ -55,8 +55,6 @@ public class ModdedPlayerOption : ModdedOption<int>
Transform container)
{
var playerOption = Object.Instantiate(playerOpt, container);
playerOption.name =
$"{ParentMod!.OptionsTitleText}.PlayerOption.{TranslationController.Instance.GetString(StringName)}";
var filteredList = GetFilteredPlayers();
Values = filteredList.ToIl2CppList();
@@ -1,177 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Networking;
using Reactor.Localization.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// An option for selecting an enum value.
/// </summary>
public class ModdedStringOption : ModdedOption<string>
{
/// <summary>
/// Gets or sets the string values of the option.
/// </summary>
public string[] Values { get; set; }
/// <summary>
/// Gets or sets the String Name values of the option.
/// </summary>
public StringNames[] StringNameValues { get; set; }
/// <summary>
/// Gets the string's index
/// </summary>
/// <param name="value">The option's string value.</param>
/// <returns>The option's value in the index.</returns>
public int GetIndex(string value, int location)
{
if (Values.Contains(value))
{
return Values.ToList().IndexOf(value);
}
Info($"Cannot find a valid index for {value} in {location}. Returning 0 instead. Values: {string.Join(", ", Values)}");
return 0;
}
/// <summary>
/// Adds a value to the option.
/// </summary>
/// <param name="value">The option's string value.</param>
/// <param name="index">The additional value's index on the list.</param>
public void RegisterExtraValue(string value, int index = -1)
{
if (!Values.Contains(value))
{
if (index <= -1)
index = Values.Length;
var newVals = Values.ToList();
newVals.Insert(index, value);
Values = newVals.ToArray();
var newStrVals = StringNameValues.ToList();
newStrVals.Insert(index, CustomStringName.CreateAndRegister(value));
StringNameValues = newStrVals.ToArray();
}
if (Data != null)
{
var data = (StringGameSetting)Data;
data.Values = StringNameValues;
data.Index = GetIndex(Value, 1);
if (OptionBehaviour != null)
{
var strOpt = OptionBehaviour.TryCast<StringOption>();
if (strOpt != null)
{
strOpt.Values = StringNameValues;
strOpt.Value = GetIndex(Value, 2);
Warning("StringOption is valid");
}
else
{
Error("StringOption is null.");
}
Warning("OptionBehaviour is valid");
}
else
{
Error("OptionBehaviour is null.");
}
Warning("Data is valid");
}
else
{
Error("Data is null.");
}
}
/// <summary>
/// Initializes a new instance of the <see cref="ModdedStringOption"/> class.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as a string.</param>
/// <param name="values">An option list of string values to use.</param>
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
public ModdedStringOption(string title, string defaultValue, string[] values, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
{
Values = values;
Data = ScriptableObject.CreateInstance<StringGameSetting>();
var data = (StringGameSetting)Data;
data.Title = StringName;
data.Type = global::OptionTypes.String;
StringNameValues = values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Values = StringNameValues;
data.Index = GetIndex(Value, 3);
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.name =
$"{ParentMod!.OptionsTitleText}.StringOption.{TranslationController.Instance.GetString(StringName)}";
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
// SetUpFromData method doesnt work correctly so we must set the values manually
stringOption.Title = StringName;
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
stringOption.Value = GetIndex(Value, 4);
OptionBehaviour = stringOption;
ModdedOptionsManager.CreatedStringOptions.TryAdd(stringOption, this);
return stringOption;
}
/// <inheritdoc />
public override float GetFloatData()
{
return Values.IndexOf(Value);
}
/// <inheritdoc />
public override NetData GetNetData()
{
return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value, NumberFormatInfo.InvariantInfo)!));
}
/// <inheritdoc />
public override void HandleNetData(byte[] data)
{
SetValue(Encoding.Unicode.GetString(data));
}
/// <inheritdoc />
public override string GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return Values[optionBehaviour.GetInt()];
}
/// <inheritdoc />
protected override void OnValueChanged(string newValue)
{
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(GetIndex(newValue, 5)), false);
if (!OptionBehaviour)
{
return;
}
if (OptionBehaviour is StringOption opt)
{
opt.Value = GetIndex(newValue, 5);
}
}
}
@@ -29,8 +29,6 @@ public class ModdedToggleOption : ModdedOption<bool>
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
{
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
toggleOption.name =
$"{ParentMod!.OptionsTitleText}.ToggleOption.{TranslationController.Instance.GetString(StringName)}";
toggleOption.SetUpFromData(Data, 20);
-17
View File
@@ -1,17 +0,0 @@
namespace MiraAPI.Hud;
/// <summary>
/// The uses mode for custom buttons.
/// </summary>
public enum ButtonUsesMode
{
/// <summary>
/// Uses per game.
/// </summary>
PerGame,
/// <summary>
/// Uses per round.
/// </summary>
PerRound,
}
+16 -127
View File
@@ -2,14 +2,9 @@
using System.Globalization;
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Keybinds;
using MiraAPI.LocalSettings;
using MiraAPI.Patches;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using Rewired;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
@@ -37,12 +32,6 @@ public abstract class CustomActionButton
/// </summary>
public abstract float Cooldown { get; }
/// <summary>
/// Gets a value indicating whether the timer should stop decreasing when the player is venting.
/// Note that both cooldown decrease and effect timer decrease is disabled.
/// </summary>
public virtual bool PauseTimerInVent => false;
/// <summary>
/// Gets the sprite of the button. Use <see cref="LoadableResourceAsset"/> to load a sprite from a resource path. Use <see cref="LoadableBundleAsset{T}"/> to load a sprite from an asset bundle.
/// </summary>
@@ -54,34 +43,14 @@ public abstract class CustomActionButton
public virtual string CooldownTimerFormatString => "0";
/// <summary>
/// Gets a value indicating whether the button has an effect ability. By default true if <see cref="EffectDuration"/> is greater than 0.
/// </summary>
public virtual bool HasEffect => EffectDuration > 0;
/// <summary>
/// Gets the button's effect duration in seconds. If 0, the effect needs to be ended manually.
/// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
/// </summary>
public virtual float EffectDuration => 0;
/// <summary>
/// Gets a value indicating whether the button has limited uses.
/// Gets the maximum amount of uses the button has. If the button has infinite uses, set to 0.
/// </summary>
public bool LimitedUses => ZeroIsInfinite ? MaxUses > 0 : MaxUses >= 0;
/// <summary>
/// Gets the maximum number of uses the button has. If the button has infinite uses, set to 0 or -1 based on what value that ZeroIsInfinite is set to.
/// </summary>
public virtual int MaxUses => ZeroIsInfinite ? 0 : -1;
/// <summary>
/// Gets the value indicating uses mode.
/// </summary>
public virtual ButtonUsesMode UsesMode => ButtonUsesMode.PerGame;
/// <summary>
/// Gets the keybind for this button. If null, no keybind will be added.
/// </summary>
public virtual BaseKeybind? Keybind => null;
public virtual int MaxUses => 0;
/// <summary>
/// Gets the button's text outline color.
@@ -94,9 +63,14 @@ public abstract class CustomActionButton
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
/// <summary>
/// Gets or sets a value indicating whether limited uses are determined via zero or a negative number of uses.
/// Gets a value indicating whether the button has an effect ability.
/// </summary>
public virtual bool ZeroIsInfinite { get; set; } = true;
public bool HasEffect => EffectDuration > 0;
/// <summary>
/// Gets a value indicating whether the button has limited uses.
/// </summary>
public bool LimitedUses => MaxUses > 0;
/// <summary>
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
@@ -123,16 +97,6 @@ public abstract class CustomActionButton
/// </summary>
public ActionButton? Button { get; set; }
/// <summary>
/// Gets the gameObject used for the keybind icon.
/// </summary>
public GameObject? KeybindIcon { get; private set; }
/// <summary>
/// Gets or sets the keybind icon text.
/// </summary>
private TextMeshPro? KeybindText { get; set; }
/// <summary>
/// The method used to create the button.
/// </summary>
@@ -204,59 +168,6 @@ public abstract class CustomActionButton
}
}
}));
if (Keybind != null)
{
Keybind.OnActivate(() =>
{
if (Enabled(PlayerControl.LocalPlayer.Data.Role))
{
// Invoke the generic button click event.
var genericEvent = new MiraButtonClickEvent(this);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this));
}
// Invoke the button click event for specific button.
var eventType = CustomButtonManager.ButtonEventTypes[GetType()];
var @event = (MiraCancelableEvent)Activator.CreateInstance(eventType, this, genericEvent)!;
var specificInvoked = MiraEventManager.InvokeEvent(@event, eventType);
if (@event.IsCancelled)
{
var cancelEventType = CustomButtonManager.ButtonCancelledEventTypes[GetType()];
var cancelEvent = (MiraEvent)Activator.CreateInstance(cancelEventType, this)!;
MiraEventManager.InvokeEvent(cancelEvent, cancelEventType);
}
if (specificInvoked)
{
if (!@event.IsCancelled)
{
ClickHandler();
}
}
else
{
if (!genericEvent.IsCancelled)
{
ClickHandler();
}
}
}
});
KeybindIcon =
Helpers.CreateKeybindIcon(
Button.gameObject,
Keybind.CurrentKey,
new Vector3(0.4f, 0.45f, -9f)
);
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
HudManagerPatches.ModdedKeybindIcons.Add(KeybindText);
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
}
}
/// <summary>
@@ -271,7 +182,7 @@ public abstract class CustomActionButton
return;
}
Location = location;
this.Location = location;
if (!moveButton)
{
@@ -443,13 +354,6 @@ public abstract class CustomActionButton
{
}
/// <summary>
/// Returns a value indicating whether the effect can be ended early by the player.
/// Always false by default.
/// </summary>
/// <returns>Can the effect be canceled.</returns>
public virtual bool IsEffectCancellable() => false;
/// <summary>
/// When the button is usable, this method is called to determine if the button can be clicked.
/// By default, it takes into account the timer, effect, and uses.
@@ -458,7 +362,7 @@ public abstract class CustomActionButton
/// <returns>A value that represents whether the button can be clicked or not.</returns>
public virtual bool CanClick()
{
return (EffectActive ? IsEffectCancellable() : Timer <= 0) && CanUse();
return Timer <= 0 && !EffectActive && CanUse();
}
/// <summary>
@@ -468,8 +372,7 @@ public abstract class CustomActionButton
/// <returns>A value that represents whether the button should light up or not.</returns>
public virtual bool CanUse()
{
return PlayerControl.LocalPlayer.moveable &&
((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0)));
return PlayerControl.LocalPlayer.moveable && (!LimitedUses || UsesLeft > 0);
}
/// <summary>
@@ -491,12 +394,6 @@ public abstract class CustomActionButton
/// </summary>
public virtual void ClickHandler()
{
if (EffectActive && IsEffectCancellable())
{
ResetCooldownAndOrEffect();
return;
}
if (!CanClick())
{
return;
@@ -529,22 +426,14 @@ public abstract class CustomActionButton
/// <param name="playerControl">The local PlayerControl.</param>
public virtual void FixedUpdateHandler(PlayerControl playerControl)
{
if (Keybind != null && KeybindText != null)
{
KeybindText.text = Keybind.CurrentKey.ToString();
KeybindIcon?.SetActive(ActiveInputManager.currentControlType is ActiveInputManager.InputType.Keyboard &&
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value &&
Keybind.CurrentKey != KeyboardKeyCode.None);
}
if (Timer >= 0)
{
if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent))
if (!TimerPaused)
{
Timer -= Time.deltaTime;
}
}
else if (HasEffect && EffectActive && EffectDuration > 0)
else if (HasEffect && EffectActive)
{
EffectActive = false;
Timer = Cooldown;
@@ -562,7 +451,7 @@ public abstract class CustomActionButton
Button!.SetDisabled();
}
if (EffectActive && EffectDuration > 0)
if (EffectActive)
{
Button.SetFillUp(Timer, EffectDuration);
-1
View File
@@ -4,7 +4,6 @@ using System.Collections.ObjectModel;
using System.Reflection;
using MiraAPI.Events.Mira;
using MiraAPI.PluginLoading;
using UnityEngine;
namespace MiraAPI.Hud;
-78
View File
@@ -1,78 +0,0 @@
using System.Linq;
using HarmonyLib;
using Reactor.Utilities.Extensions;
using UnityEngine;
namespace MiraAPI.Hud;
/// <summary>
/// Custom action button that has multiple target objects.
/// </summary>
/// <typeparam name="T">The type of the target objects.</typeparam>
public abstract class MultiTargetButton<T> : CustomActionButton where T : MonoBehaviour
{
/// <summary>
/// Gets or sets the list of targets of the button.
/// </summary>
public T[] Targets { get; protected set; } = [];
/// <inheritdoc cref="CustomActionButton{T}.Distance"/>
public virtual float Distance => PlayerControl.LocalPlayer.Data.Role.GetAbilityDistance();
/// <summary>
/// Gets the maximum amount of targets the button can have.
/// </summary>
public virtual int MaxTargets { get; } = int.MaxValue;
/// <inheritdoc cref="CustomActionButton{T}.IsTargetValid"/>
public virtual bool IsTargetValid(T target)
{
return target != null;
}
/// <summary>
/// The method used to get the target objects.
/// </summary>
/// <returns>The list of target objects.</returns>
public abstract T[] GetTargets();
/// <inheritdoc cref="CustomActionButton{T}.SetOutline"/>
/// <param name="target">The target object to set the oultine.</param>
public abstract void SetOutline(T target, bool active);
/// <inheritdoc cref="CustomActionButton{T}.CanUse"/>
public override bool CanUse()
{
var newTargets = GetTargets();
foreach (var target in Targets)
{
if (!newTargets.Contains(target))
{
SetOutline(target, false);
}
}
Targets = newTargets
.Where(IsTargetValid)
.Take(MaxTargets)
.ToArray();
Targets.Do(t => SetOutline(t, true));
return base.CanUse() && Targets.Length > 0;
}
/// <inheritdoc cref="CustomActionButton{T}.CanClick"/>
public override bool CanClick()
{
return base.CanClick() && Targets.Length > 0;
}
/// <summary>
/// Use this to reset the button's targets after used.
/// </summary>
public virtual void ResetTarget()
{
Targets.Do(t => SetOutline(t, false));
Targets = [];
}
}
-72
View File
@@ -1,72 +0,0 @@
using System;
using Rewired;
namespace MiraAPI.Keybinds;
/// <summary>
/// Base keybind class.
/// </summary>
public class BaseKeybind
{
/// <summary>
/// Gets the Rewired <see cref="InputAction"/> assinged for this keybind.
/// </summary>
public InputAction? RewiredInputAction { get; internal set; }
/// <summary>
/// Gets the unique identifier for this keybind. Used in Rewired.
/// </summary>
public string Id { get; protected set; }
/// <summary>
/// Gets the currently assigned keycode.
/// </summary>
public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this);
/// <summary>
/// Gets or sets the handler of the keybind. Invoked when the keybind is activated.
/// </summary>
protected Action Handler { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="BaseKeybind"/> class.
/// </summary>
/// <param name="id">The unique identifier for this keybind.</param>
public BaseKeybind(string id)
{
Id = id;
Handler = () => { };
}
public BaseKeybind()
{
Handler = () => { };
}
/// <summary>
/// Triggers the keybind, invoking its handler.
/// </summary>
public void Invoke()
{
Handler.Invoke();
}
/// <summary>
/// Adds an <see cref="Action"/> that will be invoked when the keybind is used.
/// </summary>
/// <param name="action">The <see cref="Action"/> to add.</param>
public void OnActivate(Action action)
{
Handler += action;
}
/// <summary>
/// Removes an <see cref="Action"/> that would be invoked when the keybind is used.
/// Not recommended to use - add checks in the method added in <see cref="OnActivate"/> itself rather than removing it from the keybind.
/// </summary>
/// <param name="action">The <see cref="Action"/> to remove.</param>
public void RemoveOnActivate(Action action)
{
Handler -= action;
}
}
-98
View File
@@ -1,98 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Rewired;
namespace MiraAPI.Keybinds;
/// <summary>
/// Lets you register and manage mod keybinds with conflict checks and rebinding support.
/// </summary>
public static class KeybindManager
{
/// <summary>
/// Gets a list of all registered keybinds.
/// </summary>
public static List<MiraKeybind> Keybinds { get; } = new();
[HideFromIl2Cpp]
internal static Dictionary<Type, VanillaKeybind> VanillaKeybinds { get; set; } = [];
internal static void RewiredInit()
{
try
{
var instance = KeybindUtils.RewiredInputManager!;
foreach (var keybind in Keybinds)
{
if (instance.userData.actions.ToArray().Any(x => x.name == keybind.Id))
{
Warning($"Keybind of id {keybind.Id} already exists. Skipping it");
continue;
}
keybind.RewiredInputAction = instance.userData.RegisterModBind(
keybind.Id,
keybind.Name,
keybind.SourcePluginName,
keybind.DefaultKey,
modifiers: keybind.ModifierKeys);
}
}
catch (Exception e)
{
Error($"Error while registering keybinds in Rewired: {e}");
}
}
/// <summary>
/// Returns all conflicts where keybinds use the same key.
/// </summary>
/// <returns>A list of pairs of keybinds that conflict.</returns>
public static Dictionary<KeyboardKeyCode, List<MiraKeybind>> GetConflicts()
{
var conflicts = new Dictionary<KeyboardKeyCode, List<MiraKeybind>>();
foreach (var keybind in Keybinds)
{
var all = GetKeybindsForKey(keybind.CurrentKey, true);
if (all.Length > 1)
{
if (!conflicts.TryGetValue(keybind.CurrentKey, out var group))
{
conflicts.Add(keybind.CurrentKey, new List<MiraKeybind>());
group = conflicts[keybind.CurrentKey];
}
if (!group.Contains(keybind))
{
group.Add(keybind);
}
}
}
return conflicts;
}
/// <summary>
/// Returns all keybinds for a specified keycode.
/// </summary>
/// <param name="keyCode">The key to look for.</param>
/// /// <param name="exclusiveCheck">Should only exlusive keybinds be present.</param>
/// <returns>The list of keybinds.</returns>
public static MiraKeybind[] GetKeybindsForKey(KeyboardKeyCode keyCode, bool exclusiveCheck = false)
{
var all = new List<MiraKeybind>();
foreach (var keybind in Keybinds)
{
if (keybind.CurrentKey == keyCode && (keybind.Exclusive || !exclusiveCheck))
{
all.Add(keybind);
}
}
return all.ToArray();
}
}
-80
View File
@@ -1,80 +0,0 @@
using System;
using Rewired;
namespace MiraAPI.Keybinds;
/// <summary>
/// Collection of keybind-related utility classes.
/// </summary>
public static class KeybindUtils
{
/// <summary>
/// Gets the Rewired <see cref="InputManager_Base"/> instance.
/// </summary>
public static InputManager_Base? RewiredInputManager { get; internal set; }
/// <summary>
/// Returns the currently assigned keycode of a keybind.
/// </summary>
/// <param name="keybind">The keybind to get the keycode from.</param>
/// <returns>The currently assigned keycode.</returns>
public static KeyboardKeyCode GetKeycodeByKeybind(BaseKeybind keybind)
{
if (keybind.RewiredInputAction == null)
{
return KeyboardKeyCode.None;
}
return GetKeycodeByActionId(keybind.RewiredInputAction.id);
}
/// <summary>
/// Gets the keycode for an action with ReInput.
/// </summary>
/// <param name="actionId">The action ID.</param>
/// <returns>The keyboard keycode.</returns>
public static KeyboardKeyCode GetKeycodeByActionId(int actionId)
{
return GetActionElementMap(actionId)?.keyboardKeyCode ?? KeyboardKeyCode.None;
}
/// <summary>
/// Gets a <see cref="ActionElementMap"/> by id with ReInput.
/// </summary>
/// <param name="actionId">The action ID.</param>
/// <returns>The keyboard keycode.</returns>
public static ActionElementMap? GetActionElementMap(int actionId)
{
var player = ReInput.players.GetPlayer(0);
return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false);
}
/// <summary>
/// Gets an <see cref="InputAction"/> by id.
/// </summary>
/// <param name="id">The action identifier name.</param>
/// <returns>The keyboard keycode.</returns>
public static InputAction? GetInputActionById(int id)
{
return RewiredInputManager?.userData?.GetActionById(id);
}
/// <summary>
/// Finds and returns an unused KeyCode that is not equal to the excluded key.
/// </summary>
/// <param name="exclude">The KeyCode to skip during the search.</param>
/// <returns>
/// The first available KeyCode not currently used by any registered keybind,
/// or KeyCode.None if none are available.
/// </returns>
public static KeyboardKeyCode FindAvailableKey(KeyboardKeyCode exclude)
{
foreach (KeyboardKeyCode key in Enum.GetValues<KeyboardKeyCode>())
{
if (key == exclude) continue;
bool used = KeybindManager.Keybinds.Exists(e => e.DefaultKey == key);
if (!used) return key;
}
return KeyboardKeyCode.None;
}
}
-41
View File
@@ -1,41 +0,0 @@
using Rewired;
namespace MiraAPI.Keybinds;
/// <summary>
/// Class containing all default keybinds.
/// Recommended for cross-mod compatibility.
/// </summary>
[RegisterCustomKeybinds]
public static class MiraGlobalKeybinds
{
/// <summary>
/// Gets the keybind for primary abilities.
/// </summary>
public static MiraKeybind PrimaryAbility { get; } = new("Primary Ability", KeyboardKeyCode.T);
/// <summary>
/// Gets the keybind for secondary abilities.
/// </summary>
public static MiraKeybind SecondaryAbility { get; } = new("Secondary Ability", KeyboardKeyCode.Y);
/// <summary>
/// Gets the keybind for Tetiary abilities.
/// </summary>
public static MiraKeybind TertiaryAbility { get; } = new("Tertiary Ability", KeyboardKeyCode.U);
/// <summary>
/// Gets the keybind for primary modifier abilities.
/// </summary>
public static MiraKeybind ModifierPrimaryAbility { get; } = new("Modifier Primary Ability", KeyboardKeyCode.I);
/// <summary>
/// Gets the keybind for secondary modifier abilities.
/// </summary>
public static MiraKeybind ModifierSecondaryAbility { get; } = new("Modifier Secondary Ability", KeyboardKeyCode.O);
/// <summary>
/// Gets the keybind for Tetiary modifier abilities.
/// </summary>
public static MiraKeybind ModifierTertiaryAbility { get; } = new("Modifier Tertiary Ability", KeyboardKeyCode.P);
}
-58
View File
@@ -1,58 +0,0 @@
using System.Globalization;
using Rewired;
namespace MiraAPI.Keybinds;
/// <summary>
/// Represents a registered keybind.
/// </summary>
public class MiraKeybind : BaseKeybind
{
/// <summary>
/// Gets name of the keybind.
/// </summary>
public string Name { get; }
/// <summary>
/// Gets the default keycode of this keybind.
/// </summary>
public KeyboardKeyCode DefaultKey { get; }
/// <summary>
/// Gets the modifier keys assigned to this keybind.
/// Due to Rewired limitations, there can only be 3 modifier keys.
/// </summary>
public ModifierKey[] ModifierKeys { get; }
/// <summary>
/// Gets a value indicating whether this keybind should be checked for conflicts with other exclusive keybinds using the same key.
/// </summary>
public bool Exclusive { get; }
internal string? SourcePluginName { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraKeybind"/> class.
/// </summary>
/// <param name="name">The name of the keybind.</param>
/// <param name="defaultKeycode">The default keycode.</param>
/// <param name="modifierKeys">Up to 3 optional modifier keys.</param>
/// <param name="exclusive">Is exclusive.</param>
public MiraKeybind(
string name,
KeyboardKeyCode? defaultKeycode,
ModifierKey[]? modifierKeys = null,
bool exclusive = true) : base(name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_'))
{
Name = name;
DefaultKey = defaultKeycode ?? KeyboardKeyCode.None;
ModifierKeys = modifierKeys ?? [];
Exclusive = exclusive;
}
/// <inheritdoc/>
public override string ToString()
{
return $"{Name} ({Id})";
}
}
@@ -1,9 +0,0 @@
using System;
namespace MiraAPI.Keybinds;
/// <summary>
/// Used to mark a class for custom keybinds registration.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class RegisterCustomKeybindsAttribute : Attribute;
-24
View File
@@ -1,24 +0,0 @@
using Rewired;
namespace MiraAPI.Keybinds;
/// <summary>
/// Represents a vanilla keybind.
/// </summary>
public class VanillaKeybind : BaseKeybind
{
/// <summary>
/// Gets the <see cref="ActionButton"/> this keybind is binded to.
/// </summary>
public ActionButton? Button { get; internal set; }
/// <summary>
/// Initializes a new instance of the <see cref="VanillaKeybind"/> class.
/// </summary>
/// <param name="id">The keybind id.</param>
public VanillaKeybind(int id)
{
RewiredInputAction = KeybindUtils.GetInputActionById(id);
Id = RewiredInputAction != null ? RewiredInputAction.name : id.ToString();
}
}
@@ -1,17 +0,0 @@
namespace MiraAPI.Keybinds;
/// <summary>
/// Singleton for getting a vanilla keybind by <see cref="ActionButton"/>.
/// </summary>
/// <typeparam name="T">ActionButton type.</typeparam>
public static class VanillaKeybinding<T> where T : ActionButton
{
private static VanillaKeybind? _instance;
/// <summary>
/// Gets the instance of the option group.
/// </summary>
#pragma warning disable CA1000
public static VanillaKeybind Instance => _instance ??= KeybindManager.VanillaKeybinds[typeof(T)];
#pragma warning restore CA1000
}
@@ -1,27 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.LocalSettings.SettingTypes;
namespace MiraAPI.LocalSettings.Attributes;
/// <summary>
/// Creates an enum setting for the <see cref="ConfigEntry{T}"/>.
/// </summary>
/// <param name="names">Optional custom enum names.</param>
/// <inheritdoc/>
[AttributeUsage(AttributeTargets.Property)]
public class LocalEnumSettingAttribute(
string? name = null,
string? description = null,
string[]? names = null
) : LocalSettingAttribute(name, description)
{
private readonly string? _name = name;
private readonly string? _description = description;
/// <inheritdoc/>
public override LocalEnumSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
{
return new LocalEnumSetting(tab, configEntryBase, configEntryBase.SettingType, _name, _description, names);
}
}
@@ -1,36 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.LocalSettings.SettingTypes;
using MiraAPI.Utilities;
namespace MiraAPI.LocalSettings.Attributes;
/// <summary>
/// Creates a number setting for the <see cref="ConfigEntry{T}"/>.
/// </summary>
/// <param name="min">Minimum range.</param>
/// <param name="max">Maximum range.</param>
/// <param name="increment">Increment per use.</param>
/// <param name="suffixType">Suffix for the value.</param>
/// <param name="formatString">Format string used when formating.</param>
/// <inheritdoc/>
[AttributeUsage(AttributeTargets.Property)]
public class LocalNumberSettingAttribute(
string? name = null,
string? description = null,
float min = 1,
float max = 5,
float increment = 1,
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
string? formatString = null
) : LocalSettingAttribute(name, description)
{
private readonly string? _name = name;
private readonly string? _description = description;
/// <inheritdoc/>
public override LocalNumberSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
{
return new LocalNumberSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), increment, suffixType, formatString);
}
}
@@ -1,24 +0,0 @@
using System;
using BepInEx.Configuration;
namespace MiraAPI.LocalSettings.Attributes;
/// <summary>
/// Base class for all local settings attributes.
/// </summary>
/// <param name="name">The name of the setting. Defaults to entry key.</param>
/// <param name="description">The description of the setting. Defalts to entry description.</param>
[AttributeUsage(AttributeTargets.Property)]
public abstract class LocalSettingAttribute(
string? name = null,
string? description = null
) : Attribute
{
/// <summary>
/// Returns the created setting object.
/// </summary>
/// <param name="tab">Gets the tab where the setting is located.</param>
/// <param name="configEntryBase">Gets the config entry the setting is attached to.</param>
/// <returns>The created class.</returns>
public abstract ILocalSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase);
}
@@ -1,12 +0,0 @@
using System;
using BepInEx.Configuration;
namespace MiraAPI.LocalSettings.Attributes;
/// <summary>
/// Attribute for local setting buttons.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class LocalSettingsButtonAttribute : Attribute
{
}
@@ -1,36 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.LocalSettings.SettingTypes;
using MiraAPI.Utilities;
namespace MiraAPI.LocalSettings.Attributes;
/// <summary>
/// Creates a slider setting for the <see cref="ConfigEntry{T}"/>.
/// </summary>
/// <inheritdoc/>
/// <param name="min">Minimum range.</param>
/// <param name="max">Maximum range.</param>
/// <param name="roundValue">Should the value be rounded.</param>
/// <param name="suffixType">Suffix for the value.</param>
[AttributeUsage(AttributeTargets.Property)]
public class LocalSliderSettingAttribute(
string? name = null,
float min = 0,
float max = 100,
string? description = null,
bool displayValue = false,
string? formatString = null,
bool roundValue = false,
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None
) : LocalSettingAttribute(name, description)
{
private readonly string? _name = name;
private readonly string? _description = description;
/// <inheritdoc/>
public override LocalSliderSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
{
return new LocalSliderSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), displayValue, suffixType, formatString, roundValue);
}
}
@@ -1,25 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.LocalSettings.SettingTypes;
namespace MiraAPI.LocalSettings.Attributes;
/// <summary>
/// Creates a toggle setting for the <see cref="ConfigEntry{T}"/>.
/// </summary>
/// <inheritdoc/>
[AttributeUsage(AttributeTargets.Property)]
public class LocalToggleSettingAttribute(
string? name = null,
string? description = null
) : LocalSettingAttribute(name, description)
{
private readonly string? _name = name;
private readonly string? _description = description;
/// <inheritdoc/>
public override LocalToggleSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
{
return new LocalToggleSetting(tab, configEntryBase, _name, _description);
}
}
-42
View File
@@ -1,42 +0,0 @@
using BepInEx.Configuration;
using UnityEngine;
namespace MiraAPI.LocalSettings;
/// <summary>
/// Interface for all local settings.
/// </summary>
public interface ILocalSetting
{
/// <summary>
/// Gets the preferred name of a setting.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the description of a setting.
/// </summary>
string Description { get; }
/// <summary>
/// Gets the setting's gameobject.
/// </summary>
GameObject? Setting { get; }
/// <summary>
/// Gets the setting's config entry.
/// </summary>
ConfigEntryBase ConfigEntry { get; }
/// <summary>
/// Used to create the setting.
/// </summary>
/// <param name="toggle">Toggle template.</param>
/// <param name="slider">Slider template.</param>
/// <param name="parent">The parent of the setting.</param>
/// <param name="offset">The offset.</param>
/// <param name="order">The order.</param>
/// <param name="last">Whether it's the last on the row.</param>
/// <returns>The created setting.</returns>
GameObject? CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last);
}
@@ -1,78 +0,0 @@
using System;
using MiraAPI.Utilities;
using Reactor.Localization.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace MiraAPI.LocalSettings;
/// <summary>
/// Represents a local settings button.
/// </summary>
public class LocalSettingsButton(string text, Action onClick)
{
/// <summary>
/// Gets or sets the button text.
/// </summary>
public string Text { get; set; } = text;
/// <summary>
/// Gets or sets the button on-click action.
/// </summary>
public Action OnClick { get; set; } = onClick;
/// <summary>
/// Gets the tab instance this button belongs to.
/// </summary>
public LocalSettingsTab? Tab { get; internal set; }
internal GameObject CreateButton(ToggleButtonBehaviour toggle, Transform parent, ref float offset, ref int order, bool last)
{
var button = Object.Instantiate(toggle, parent).GetComponent<PassiveButton>();
var tmp = button.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
var rollover = button.GetComponent<ButtonRolloverHandler>();
tmp.GetComponent<TextTranslatorTMP>().Destroy();
button.gameObject.SetActive(true);
var toggleComp = button.GetComponent<ToggleButtonBehaviour>();
var background = toggleComp.Background;
button.transform.FindChild("ButtonHighlight")?.gameObject.DestroyImmediate();
toggleComp.Destroy();
if (last && order == 1)
{
// Button in the middle
button.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
}
else
{
button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
}
tmp.text = Text;
button.name = Text;
button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
rollover.OutColor = Tab!.TabAppearance.ButtonColor;
rollover.OverColor = Tab!.TabAppearance.ButtonHoverColor;
rollover.Target = background;
background.color = Tab!.TabAppearance.ButtonColor;
button.OnClick.AddListener((UnityAction)OnClick);
background.transform.localScale = new Vector3(1, 1.25f, 1);
Helpers.DivideSize(button.gameObject, 1.1f);
order++;
if (order > 2 && !last)
{
offset += 0.5f;
order = 1;
}
if (last)
offset += 0.6f;
return button.gameObject;
}
}
@@ -1,42 +0,0 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using BepInEx.Unity.IL2CPP;
using MiraAPI.GameOptions;
using Reactor.Utilities;
namespace MiraAPI.LocalSettings;
/// <summary>
/// Manages local settings.
/// </summary>
public static class LocalSettingsManager
{
internal static readonly Dictionary<Type, LocalSettingsTab> TypeToTab = [];
internal static readonly List<LocalSettingsTab> Tabs = [];
internal static bool RegisterTab(Type type, BasePlugin pluginInfo)
{
if (Activator.CreateInstance(type, pluginInfo.Config) is not LocalSettingsTab tab)
{
return false;
}
if (TypeToTab.ContainsKey(type))
{
Error($"Local settings tab {type.Name} already exists.");
return false;
}
Tabs.Add(tab);
TypeToTab.Add(type, tab);
typeof(LocalSettingsTabSingleton<>).MakeGenericType(type)
#pragma warning disable S3011
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
#pragma warning restore S3011
.SetValue(null, tab);
return true;
}
}
-299
View File
@@ -1,299 +0,0 @@
using System;
using System.Collections.Generic;
using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.Patches.LocalSettings;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace MiraAPI.LocalSettings;
/// <summary>
/// Represents a local settings tab. Gets a button by default.
/// </summary>
public abstract class LocalSettingsTab(ConfigFile config)
{
/// <summary>
/// Gets the name of the tab.
/// </summary>
public abstract string TabName { get; }
/// <summary>
/// Gets the tab appearance.
/// </summary>
public virtual LocalSettingTabAppearance TabAppearance { get; } = new();
/// <summary>
/// Gets a value indicating whether a button should be created for the tab.
/// </summary>
public virtual bool ShouldCreateButton => true;
/// <summary>
/// Gets a value indicating whether the tab should have category labels.
/// </summary>
protected virtual bool ShouldCreateLabels => true;
/// <summary>
/// Gets the mod's config file. Automatically found in the plugin loader.
/// </summary>
public ConfigFile Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
/// <summary>
/// Gets the tab button instance.
/// </summary>
public TabGroup? TabButton { get; internal set; }
/// <summary>
/// Gets the list of settings.
/// </summary>
public List<ILocalSetting> Settings { get; } = [];
/// <summary>
/// Gets the list of buttons.
/// </summary>
public List<LocalSettingsButton> Buttons { get; } = [];
/// <summary>
/// Gets the index of this tab.
/// </summary>
protected int TabIndex => LocalSettingsManager.Tabs.IndexOf(this) + 10;
private Scroller? Scroller { get; set; }
/// <summary>
/// Invoked when an option value is changed.
/// </summary>
/// <param name="configEntry">The config entry which was changed.</param>
public virtual void OnOptionChanged(ConfigEntryBase configEntry)
{
}
/// <summary>
/// Attempts to open the tab.
/// Override for custom behavior.
/// </summary>
public virtual void Open()
{
OptionsMenuPatches.Instance?.OpenTabGroup(TabIndex);
}
/// <summary>
/// Creates the tab <see cref="GameObject"/> and it's content.
/// </summary>
/// <param name="instance">The <see cref="OptionsMenuBehaviour"/> instace.</param>
/// <returns>The created tab <see cref="GameObject"/>.</returns>
public virtual GameObject CreateTab(OptionsMenuBehaviour instance)
{
var tab = Object.Instantiate(instance.transform.FindChild("GeneralTab").gameObject, instance.transform);
tab.name = $"{TabName}Tab";
tab.transform.DestroyChildren();
tab.gameObject.SetActive(false);
if (Scroller == null)
{
Scroller = Helpers.CreateScroller(tab.transform, OptionsMenuPatches.MaskCollider);
}
if (TabButton != null)
{
TabButton.Content = tab.gameObject;
}
var generalLabel = instance.transform.FindChild("GeneralTab").FindChild("ControlGroup")
.FindChild("ControlText_TMP").gameObject;
var toggle = instance.transform.FindChild("GeneralTab").FindChild("ChatGroup").FindChild("CensorChatButton").GetComponent<ToggleButtonBehaviour>();
var slider = instance.transform.FindChild("GeneralTab").FindChild("SoundGroup").FindChild("SFXSlider").GetComponent<SlideBar>();
Dictionary<string, List<ILocalSetting>> entriesByGroup = new();
foreach (var entry in Settings)
{
var group = entry.ConfigEntry.Definition.Section;
if (!entriesByGroup.ContainsKey(group))
entriesByGroup.Add(group, []);
entriesByGroup[group].Add(entry);
}
float contentOffset = 0;
var contentOrder = 1;
var contentIndex = 1;
foreach (var pair in entriesByGroup)
{
if (ShouldCreateLabels)
{
CreateLabel(generalLabel, Scroller.Inner, pair.Key, ref contentOffset);
contentOrder = 1;
contentIndex = 1;
}
foreach (var setting in pair.Value)
{
var obj = setting.CreateOption(
toggle,
slider,
Scroller.Inner,
ref contentOffset,
ref contentOrder,
contentIndex == pair.Value.Count);
obj!.GetComponentsInChildren<SpriteRenderer>(true).Do(x =>
{
x.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
x.sortingOrder = 150;
x.sortingLayerName = "Default";
});
obj.GetComponentsInChildren<MeshRenderer>(true).Do(x =>
{
x.sortingOrder = 151;
x.sortingLayerName = "Default";
});
obj.GetComponentsInChildren<PassiveButton>().Do(x => { x.ClickMask = OptionsMenuPatches.MaskCollider; });
contentIndex++;
}
contentOrder = 1;
}
contentIndex = 1;
contentOrder = 1;
foreach (var button in Buttons)
{
var obj = button.CreateButton(
toggle,
Scroller.Inner,
ref contentOffset,
ref contentOrder,
contentIndex == Buttons.Count);
obj!.GetComponentsInChildren<SpriteRenderer>(true).Do(x =>
{
x.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
x.sortingOrder = 150;
x.sortingLayerName = "Default";
});
obj.GetComponentsInChildren<MeshRenderer>(true).Do(x =>
{
x.sortingOrder = 151;
x.sortingLayerName = "Default";
});
obj.GetComponentsInChildren<PassiveButton>().Do(x => { x.ClickMask = OptionsMenuPatches.MaskCollider; });
contentIndex++;
}
Scroller.SetBounds(new FloatRange(0, (Settings.Count + Buttons.Count) * 0.5f - 5f), new FloatRange(0, 0));
return tab;
}
/// <summary>
/// Creates the tab button.
/// </summary>
/// <param name="instance">The <see cref="OptionsMenuBehaviour"/> instance.</param>
/// <param name="tabIdx">The tab index.</param>
/// <param name="offset">The current button offset.</param>
/// <returns>The created button <see cref="GameObject"/>.</returns>
public virtual GameObject CreateTabButton(OptionsMenuBehaviour instance, ref int tabIdx, ref float offset)
{
var tabButtonObject = Object.Instantiate(instance.Tabs[0], instance.transform);
tabButtonObject.name = $"{TabName} Button";
tabButtonObject.transform.localPosition = new Vector3(2.4f, 2.1f - offset, 5.5f);
tabButtonObject.transform.localScale = new Vector3(1.25f, 1.25f, 1);
tabButtonObject.Button.color = TabAppearance.TabButtonColor;
TabButton = tabButtonObject;
var tabButtonText = tabButtonObject.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
tabButtonText.GetComponent<TextTranslatorTMP>().Destroy(); // i hate text translators
tabButtonText.transform.localPosition = new Vector3(0.078f, 0, 0);
tabButtonText.transform.localScale = new Vector3(0.9f, 0.9f);
tabButtonText.alignment = TextAlignmentOptions.Right;
tabButtonText.text = $"<b>{GetShortName(TabName)}</b>";
var tabButton = tabButtonObject.GetComponent<PassiveButton>();
var rollover = tabButtonObject.Rollover;
rollover.OverColor = TabAppearance.TabButtonHoverColor;
rollover.OutColor = TabAppearance.TabButtonColor;
SpriteRenderer? tabButtonRend = null;
if (TabAppearance.TabIcon != null)
{
tabButtonRend = new GameObject("sprite").AddComponent<SpriteRenderer>();
tabButtonRend.gameObject.layer = 5; // ui layer
tabButtonRend.transform.SetParent(tabButtonObject.transform);
tabButtonRend.transform.localPosition = new Vector3(0.5f, 0, -2);
tabButtonRend.transform.localScale = new Vector3(0.2f, 0.2f, 1);
tabButtonRend.sprite = TabAppearance.TabIcon.LoadAsset();
tabButtonText.gameObject.SetActive(false);
}
var tabIndex = tabIdx; // Local copies of the tab variables so we can use them in the lambdas
var tabOffset = offset;
tabButton.OnClick.AddListener((UnityAction)(() => { instance.OpenTabGroup(tabIndex + 10); }));
tabButton.OnMouseOver.AddListener((UnityAction)(() =>
{
tabButton.transform.localPosition = new Vector3(3.55f, 2.1f - tabOffset, 5.5f);
tabButtonText.gameObject.SetActive(true);
tabButtonText.transform.localPosition = new Vector3(-0.024f, 0, 0);
tabButtonText.maxVisibleCharacters = int.MaxValue;
tabButtonText.text = TabName;
tabButtonText.alignment = TextAlignmentOptions.Left;
tabButtonRend?.gameObject.SetActive(!TabAppearance.HideIconOnHover);
}));
tabButton.OnMouseOut.AddListener((UnityAction)(() =>
{
if (!tabButtonObject.Content.gameObject.activeSelf)
{
//tabButtonObject.Button.color = TabColor;
//tabButtonObject.Rollover.OutColor = TabColor;
}
tabButton.transform.localPosition = new Vector3(2.4f, 2.1f - tabOffset, 5.5f);
tabButtonText.alignment = TextAlignmentOptions.Right;
if (tabButtonRend != null)
{
tabButtonRend.gameObject.SetActive(true);
tabButtonText.gameObject.SetActive(false);
}
tabButtonText.transform.localPosition = new Vector3(0.1f, 0, 0);
tabButtonText.maxVisibleCharacters = 4;
tabButtonText.text = $"<b>{GetShortName(TabName)}</b>";
}));
return tabButtonObject.gameObject;
}
private static void CreateLabel(GameObject template, Transform parent, string text, ref float offset)
{
var label = Object.Instantiate(template, parent);
label.transform.localPosition = new Vector3(0, 1.85f - offset);
label.GetComponent<TextTranslatorTMP>().Destroy();
label.name = text;
var tmp = label.GetComponent<TextMeshPro>();
tmp.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\"><b>{text}</b></font>";
tmp.alignment = TextAlignmentOptions.Center;
var meshRend = label.GetComponent<MeshRenderer>();
meshRend.sortingLayerName = "Default";
meshRend.sortingOrder = 151;
offset += 0.5f;
}
private static string GetShortName(string name)
{
var shortName = string.Empty;
name.Split(' ').Do(x => shortName += x[0]);
return shortName;
}
}
@@ -1,96 +0,0 @@
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.LocalSettings;
/// <summary>
/// Struct for modifying tab appearance.
/// Defines how the settings look.
/// </summary>
public record struct LocalSettingTabAppearance()
{
/// <summary>
/// Gets or sets main color of the tab.
/// </summary>
public Color TabColor { get; set; } = Color.white;
/// <summary>
/// Gets or sets tab button icon. No icon by default.
/// </summary>
public LoadableAsset<Sprite>? TabIcon { get; set; }
/// <summary>
/// Gets or sets color of the tab button.
/// </summary>
public Color TabButtonColor { get; set; } = Color.white;
/// <summary>
/// Gets or sets a value indicating whether the icon should be hidden when tab button is hovered.
/// </summary>
public bool HideIconOnHover { get; set; } = true;
/// <summary>
/// Gets or sets hover color of the tab button.
/// </summary>
public Color TabButtonHoverColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets active color of the tab button.
/// </summary>
public Color TabButtonActiveColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets hover color of toggles.
/// </summary>
public Color ToggleHoverColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets active color of toggles.
/// </summary>
public Color ToggleActiveColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets inactive color of toggles.
/// </summary>
public Color ToggleInactiveColor { get; set; } = Color.red;
/// <summary>
/// Gets or sets color of number buttons.
/// </summary>
public Color NumberColor { get; set; } = Color.white;
/// <summary>
/// Gets or sets hover color of number buttons.
/// </summary>
public Color NumberHoverColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets color of sliders.
/// </summary>
public Color SliderColor { get; set; } = Color.white;
/// <summary>
/// Gets or sets hover color of sliders.
/// </summary>
public Color SliderHoverColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets color of enum buttons.
/// </summary>
public Color EnumColor { get; set; } = Color.white;
/// <summary>
/// Gets or sets hover color of enum buttons.
/// </summary>
public Color EnumHoverColor { get; set; } = Palette.AcceptedGreen;
/// <summary>
/// Gets or sets color of buttons.
/// </summary>
public Color ButtonColor { get; set; } = Color.white;
/// <summary>
/// Gets or sets hover color of buttons.
/// </summary>
public Color ButtonHoverColor { get; set; } = Palette.AcceptedGreen;
}
@@ -1,19 +0,0 @@
using System.Linq;
namespace MiraAPI.LocalSettings;
/// <summary>
/// Singleton for local settings tabs.
/// </summary>
/// <typeparam name="T">The settings tab type.</typeparam>
public static class LocalSettingsTabSingleton<T> where T : LocalSettingsTab
{
private static T? _instance;
/// <summary>
/// Gets the instance of the setting tab.
/// </summary>
#pragma warning disable CA1000
public static T Instance => _instance ??= LocalSettingsManager.Tabs.OfType<T>().Single();
#pragma warning restore CA1000
}
@@ -1,132 +0,0 @@
using System;
using System.Linq;
using BepInEx.Configuration;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace MiraAPI.LocalSettings.SettingTypes;
/// <summary>
/// Local setting class for enums.
/// </summary>
public class LocalEnumSetting : LocalSettingBase<int>
{
/// <summary>
/// Gets the enum type of the setting.
/// </summary>
public Type EnumType { get; }
/// <summary>
/// Gets the enum values.
/// </summary>
public string[] Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LocalEnumSetting"/> class.
/// </summary>
/// <inheritdoc/>
/// <param name="enumType">The enum type.</param>
/// <param name="values">The optional values array to replace the enum names.</param>
public LocalEnumSetting(
Type tab,
ConfigEntryBase configEntry,
Type enumType,
string? name = null,
string? description = null,
string[]? values = null)
: base(tab, configEntry, name, description)
{
EnumType = enumType;
Values = values ?? Enum
.GetValues(configEntry.SettingType)
.Cast<Enum>()
.Select(x => x.ToDisplayString())
.ToArray();
}
/// <inheritdoc />
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
{
var button = Object.Instantiate(toggle, parent).GetComponent<PassiveButton>();
var tmp = button.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
var rollover = button.GetComponent<ButtonRolloverHandler>();
tmp.GetComponent<TextTranslatorTMP>().Destroy();
button.gameObject.SetActive(true);
var toggleComp = button.GetComponent<ToggleButtonBehaviour>();
var background = toggleComp.Background;
var highlight = button.transform.FindChild("ButtonHighlight")?.GetComponent<SpriteRenderer>();
if (highlight != null)
{
highlight.color = Tab!.TabAppearance.EnumHoverColor;
highlight.gameObject.SetActive(false);
}
toggleComp.Destroy();
if (last && order == 1)
{
// Button in the middle
button.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
}
else
{
button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
}
tmp.text = GetValueText();
button.name = Name;
button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
rollover.OutColor = Tab!.TabAppearance.EnumColor;
rollover.OverColor = Tab!.TabAppearance.EnumHoverColor;
background.color = Tab!.TabAppearance.EnumColor;
button.OnClick.AddListener((UnityAction)(() =>
{
int value = GetValue();
value++;
if (value >= Values.Length)
{
value = 0;
}
SetValue(value);
tmp.text = GetValueText();
}));
button.OnMouseOver.AddListener((UnityAction)(() =>
{
if (!Description.IsNullOrWhiteSpace())
{
tmp.text = Description;
}
highlight?.gameObject.SetActive(true);
}));
button.OnMouseOut.AddListener((UnityAction)(() =>
{
tmp.text = GetValueText();
highlight?.gameObject.SetActive(false);
}));
Helpers.DivideSize(button.gameObject, 1.1f);
order++;
if (order > 2 && !last)
{
offset += 0.5f;
order = 1;
}
if (last)
offset += 0.6f;
return button.gameObject;
}
/// <inheritdoc/>
protected override string GetValueText()
{
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}: <b>{Values[GetValue()]}</font></b>";
}
}
@@ -1,145 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace MiraAPI.LocalSettings.SettingTypes;
/// <summary>
/// Local setting class for numbers.
/// </summary>
public class LocalNumberSetting : LocalSettingBase<float>
{
/// <summary>
/// Gets the range of the button.
/// </summary>
public FloatRange NumberRange { get; }
/// <summary>
/// Gets the increment of the value when button is pressed.
/// </summary>
public float Increment { get; }
/// <summary>
/// Gets a format for the text to use to format the number.
/// </summary>
public string FormatString { get; }
/// <summary>
/// Gets the suffix for the number value.
/// </summary>
public MiraNumberSuffixes SuffixType { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LocalNumberSetting"/> class.
/// </summary>
/// <inheritdoc/>
/// <param name="numberRange">The value range.</param>
/// <param name="increment">The increment per click.</param>
/// <param name="suffixType">The suffix used for formating.</param>
/// <param name="formatString">The format string used for formating.</param>
public LocalNumberSetting(
Type tab,
ConfigEntryBase configEntry,
string? name = null,
string? description = null,
FloatRange? numberRange = null,
float? increment = null,
MiraNumberSuffixes? suffixType = null,
string? formatString = null)
: base(tab, configEntry, name, description)
{
SuffixType = suffixType ?? MiraNumberSuffixes.None;
NumberRange = numberRange ?? new FloatRange(1, 5);
Increment = increment ?? 1;
FormatString = formatString ?? "0";
}
/// <inheritdoc />
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
{
var button = Object.Instantiate(toggle, parent).GetComponent<PassiveButton>();
var tmp = button.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
var rollover = button.GetComponent<ButtonRolloverHandler>();
tmp.GetComponent<TextTranslatorTMP>().Destroy();
button.gameObject.SetActive(true);
var toggleComp = button.GetComponent<ToggleButtonBehaviour>();
var background = toggleComp.Background;
var highlight = button.transform.FindChild("ButtonHighlight")?.GetComponent<SpriteRenderer>();
if (highlight != null)
{
highlight.color = Tab!.TabAppearance.NumberHoverColor;
highlight.gameObject.SetActive(false);
}
toggleComp.Destroy();
if (last && order == 1)
{
// Button in the middle
button.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
}
else
{
button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
}
tmp.text = GetValueText();
button.name = Name;
button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
rollover.OutColor = Tab!.TabAppearance.NumberColor;
rollover.OverColor = Tab!.TabAppearance.NumberHoverColor;
background.color = Tab!.TabAppearance.NumberColor;
button.OnClick.AddListener((UnityAction)(() =>
{
float value = GetValue();
value += Increment;
if (value > NumberRange.max)
{
value = NumberRange.min;
}
SetValue(value);
tmp.text = GetValueText();
}));
button.OnMouseOver.AddListener((UnityAction)(() =>
{
if (!Description.IsNullOrWhiteSpace())
{
tmp.text = Description;
}
highlight?.gameObject.SetActive(true);
}));
button.OnMouseOut.AddListener((UnityAction)(() =>
{
tmp.text = GetValueText();
highlight?.gameObject.SetActive(false);
}));
Helpers.DivideSize(button.gameObject, 1.1f);
order++;
if (order > 2 && !last)
{
offset += 0.5f;
order = 1;
}
if (last)
offset += 0.6f;
return button.gameObject;
}
/// <inheritdoc/>
protected override string GetValueText()
{
var value = GetValue();
var formated = Helpers.FormatValue(value, SuffixType, FormatString);
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}: <b>{formated}</font></b>";
}
}
@@ -1,76 +0,0 @@
using System;
using System.Linq;
using BepInEx.Configuration;
using UnityEngine;
namespace MiraAPI.LocalSettings.SettingTypes;
/// <inheritdoc />
public abstract class LocalSettingBase<T> : ILocalSetting
{
/// <inheritdoc />
public string Name { get; }
/// <inheritdoc />
public string Description { get; }
/// <inheritdoc />
public GameObject? Setting { get; } = null!;
/// <inheritdoc />
public ConfigEntryBase ConfigEntry { get; }
/// <summary>
/// Gets the tab of the local setting.
/// </summary>
public LocalSettingsTab? Tab => LocalSettingsManager.Tabs.FirstOrDefault(x => x.Settings.Contains(this));
/// <summary>
/// Initializes a new instance of the <see cref="LocalSettingBase{T}"/> class.
/// </summary>
/// <param name="tab">The tab to create the setting in.</param>
/// <param name="configEntry">The config entry.</param>
/// <param name="name">The name of the setting.</param>
/// <param name="description">The description of the setting.</param>
protected LocalSettingBase(Type tab, ConfigEntryBase configEntry, string? name = null, string? description = null)
{
ConfigEntry = configEntry;
Name = name ?? ConfigEntry.Definition.Key;
Description = description ?? ConfigEntry.Description.Description;
LocalSettingsManager.TypeToTab[tab].Settings.Add(this);
}
/// <summary>
/// Creates an instance of the setting.
/// </summary>
/// <returns>The created setting.</returns>
public abstract GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last);
/// <summary>
/// Returns the formated string to use in the text of the setting.
/// </summary>
/// <returns>The value text.</returns>
protected virtual string GetValueText()
{
return string.Empty;
}
/// <summary>
/// Gets the value of the config entry, cast to the setting type.
/// </summary>
/// <returns>The value.</returns>
public virtual T GetValue()
{
return (T)ConfigEntry.BoxedValue;
}
/// <summary>
/// Sets the value of the config entry.
/// </summary>
/// <param name="value">The value to set to.</param>
public virtual void SetValue(T value)
{
ConfigEntry.BoxedValue = value;
Tab?.OnOptionChanged(ConfigEntry);
}
}
@@ -1,120 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.Utilities;
using Reactor.Utilities.Extensions;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace MiraAPI.LocalSettings.SettingTypes;
/// <summary>
/// Local setting class for sliders.
/// </summary>
public class LocalSliderSetting : LocalSettingBase<float>
{
/// <summary>
/// Gets the range of the slider.
/// </summary>
public FloatRange SliderRange { get; }
/// <summary>
/// Gets a value indicating whether the value should be displayed next to name.
/// </summary>
public bool DisplayValue { get; }
/// <summary>
/// Gets a format for the text to use to format the number.
/// </summary>
public string FormatString { get; }
/// <summary>
/// Gets a value indicating whether the value should be rounded.
/// </summary>
public bool RoundValue { get; }
/// <summary>
/// Gets the suffix for the number value.
/// </summary>
public MiraNumberSuffixes SuffixType { get; }
/// <summary>
/// Initializes a new instance of the <see cref="LocalSliderSetting"/> class.
/// </summary>
/// <inheritdoc/>
/// <param name="sliderRange">The value range.</param>
/// <param name="suffixType">The suffix used for formating.</param>
/// <param name="formatString">The format string used for formating.</param>
/// <param name="roundValue">Should the value be rounded.</param>
public LocalSliderSetting(
Type tab,
ConfigEntryBase configEntry,
string? name = null,
string? description = null,
FloatRange? sliderRange = null,
bool displayValue = false,
MiraNumberSuffixes? suffixType = null,
string? formatString = null,
bool roundValue = false)
: base(tab, configEntry, name, description)
{
SliderRange = sliderRange ?? new FloatRange(0, 100);
DisplayValue = displayValue;
SuffixType = suffixType ?? MiraNumberSuffixes.None;
FormatString = formatString ?? "0.0";
RoundValue = roundValue;
}
/// <inheritdoc />
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
{
var newSlider = Object.Instantiate(slider, parent).GetComponent<SlideBar>();
var rollover = newSlider.GetComponent<ButtonRolloverHandler>();
newSlider.Title = newSlider.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>(); // Why the hell slider has a title property that is not even assigned???
newSlider.Title.GetComponent<TextTranslatorTMP>().Destroy();
newSlider.gameObject.SetActive(true);
newSlider.Bar.color = Tab!.TabAppearance.SliderColor;
if (order == 2)
offset += 0.5f;
newSlider.Bar.transform.localPosition = new Vector3(2.85f, 0, 0);
newSlider.transform.localPosition = new Vector3(-2.12f, 1.85f - offset, -7);
newSlider.name = Name;
newSlider.Range = new FloatRange(-1.5f, 1.5f);
newSlider.SetValue(Mathf.InverseLerp(SliderRange.min, SliderRange.max, GetValue()));
rollover.OutColor = Tab!.TabAppearance.SliderColor;
rollover.OverColor = Tab!.TabAppearance.SliderHoverColor;
newSlider.Title.transform.localPosition = new Vector3(0.5f, 0, -1f);
newSlider.Title.horizontalAlignment = DisplayValue ? HorizontalAlignmentOptions.Left : HorizontalAlignmentOptions.Center;
newSlider.Title.text = GetValueText();
newSlider.OnValueChange.AddListener((UnityAction)(() =>
{
SetValue(RoundValue
? Mathf.Round(Mathf.Lerp(SliderRange.min, SliderRange.max, newSlider.Value))
: Mathf.Lerp(SliderRange.min, SliderRange.max, newSlider.Value));
newSlider.Title.text = GetValueText();
}));
order = 1;
offset += 0.5f;
return newSlider.gameObject;
}
/// <inheritdoc/>
protected override string GetValueText()
{
if (DisplayValue)
{
var value = GetValue();
var formated = Helpers.FormatValue(value, SuffixType, FormatString);
var maxFormated = Helpers.FormatValue(SliderRange.max, SuffixType, FormatString);
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}: <b>{formated} / {maxFormated}</font></b>";
}
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}</font></b>";
}
}
@@ -1,88 +0,0 @@
using System;
using BepInEx.Configuration;
using MiraAPI.Utilities;
using Reactor.Localization.Utilities;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
namespace MiraAPI.LocalSettings.SettingTypes;
/// <summary>
/// Local setting class for toggles.
/// </summary>
public class LocalToggleSetting : LocalSettingBase<bool>
{
/// <summary>
/// Initializes a new instance of the <see cref="LocalToggleSetting"/> class.
/// </summary>
/// <inheritdoc/>
public LocalToggleSetting(
Type tab,
ConfigEntryBase configEntry,
string? name = null,
string? description = null
) : base(tab, configEntry, name, description)
{
}
/// <inheritdoc />
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
{
var toggleObject = Object.Instantiate(toggle, parent).GetComponent<ToggleButtonBehaviour>();
var tmp = toggleObject.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
var passiveButton = toggleObject.GetComponent<PassiveButton>();
var rollover = toggleObject.GetComponent<ButtonRolloverHandler>();
toggleObject.gameObject.SetActive(true);
if (last && order == 1)
{
// Toggle in the middle
toggleObject.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
}
else
{
toggleObject.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
}
toggleObject.BaseText = CustomStringName.CreateAndRegister(Name);
toggleObject.UpdateText(GetValue());
toggleObject.name = Name;
toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor;
passiveButton.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
rollover.OverColor = Tab!.TabAppearance.ToggleHoverColor;
passiveButton.OnClick.AddListener((UnityAction)(() =>
{
SetValue(!GetValue());
toggleObject.UpdateText(GetValue());
toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor;
}));
passiveButton.OnMouseOver.AddListener((UnityAction)(() =>
{
if (!Description.IsNullOrWhiteSpace())
{
tmp.text = Description;
}
}));
passiveButton.OnMouseOut.AddListener((UnityAction)(() =>
{
toggleObject.UpdateText(GetValue());
toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor;
}));
Helpers.DivideSize(toggleObject.gameObject, 1.1f);
order++;
if (order > 2 && !last)
{
offset += 0.5f;
order = 1;
}
if (last)
offset += 0.6f;
return toggleObject.gameObject;
}
}
+2 -14
View File
@@ -1,6 +1,4 @@
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
using System;
using BepInEx;
using BepInEx;
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
@@ -22,11 +20,6 @@ namespace MiraAPI;
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class MiraApiPlugin : BasePlugin
{
/// <summary>
/// Gets a value indicating whether the current device is running Starlight (on mobile).
/// </summary>
public static bool IsMobile => Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone;
/// <summary>
/// Gets the branding Mira API color.
/// </summary>
@@ -37,11 +30,6 @@ public partial class MiraApiPlugin : BasePlugin
/// </summary>
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
/// <summary>
/// Gets a value indicating whether the current build is a development build or not. This mostly avoids confusion for users asking why the mod appears red.
/// </summary>
public static bool IsDevBuild => Version.Contains("ci", StringComparison.OrdinalIgnoreCase) || Version.Contains("dev", StringComparison.OrdinalIgnoreCase);
private static MiraPluginManager? PluginManager { get; set; }
internal Harmony Harmony { get; } = new(Id);
@@ -50,7 +38,7 @@ public partial class MiraApiPlugin : BasePlugin
{
Harmony.PatchAll();
ReactorCredits.Register("Mira API", Version, IsDevBuild, ReactorCredits.AlwaysShow);
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
PluginManager = new MiraPluginManager();
PluginManager.Initialize();
-101
View File
@@ -1,101 +0,0 @@
using BepInEx.Configuration;
using MiraAPI.LocalSettings;
using MiraAPI.LocalSettings.Attributes;
using MiraAPI.Patches;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI;
/// <summary>
/// Mira API Config File Handler.
/// </summary>
public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
{
/// <inheritdoc />
public override string TabName => "Mira API";
/// <inheritdoc />
public override LocalSettingTabAppearance TabAppearance => new()
{
TabButtonHoverColor = MiraApiPlugin.MiraColor,
TabIcon = MiraAssets.SettingsIcon,
HideIconOnHover = false,
};
/// <summary>
/// Gets or sets the value stored for scaling buttons properly.
/// </summary>
public static float OldButtonScaleFactor { get; set; }
/// <inheritdoc />
public override void Open()
{
base.Open();
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
}
/// <inheritdoc />
public override void OnOptionChanged(ConfigEntryBase configEntry)
{
base.OnOptionChanged(configEntry);
if (configEntry == ButtonUIFactorSlider)
{
if (HudManager.InstanceExists)
{
HudManagerPatches.ResizeUI(1f / OldButtonScaleFactor);
HudManagerPatches.ResizeUI(ButtonUIFactorSlider.Value);
}
OldButtonScaleFactor = ButtonUIFactorSlider.Value;
}
else if (configEntry == SetFpsSlider)
{
Application.targetFrameRate = (int)SetFpsSlider.Value;
}
}
/// <summary>
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Visuals/UI", "Show Modifiers HUD on Left Side", false);
/// <summary>
/// Gets whether to show keybinds on buttons.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Visuals/UI", "Show Keybinds on Buttons", true);
/// <summary>
/// Gets the scale of the buttons.
/// </summary>
[LocalSliderSetting(min: 0.5f, max: 1.5f, suffixType: MiraNumberSuffixes.Multiplier, formatString: "0.00", displayValue: true)]
public ConfigEntry<float> ButtonUIFactorSlider { get; private set; } =
config.Bind("Visuals/UI", "Button Scale Factor", 0.75f);
/// <summary>
/// Gets the fps specified by the player.
/// </summary>
[LocalSliderSetting(min: 60f, max: 240f, suffixType: MiraNumberSuffixes.None, formatString: "0", displayValue: true, roundValue: true)]
public ConfigEntry<float> SetFpsSlider { get; private set; } =
config.Bind("Visuals/UI", "Max FPS", 120f);
/// <summary>
/// Gets whether to apply cosmetic changes to the TaskAdder.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true);
/// <summary>
/// Gets whether to show the red flash from sabotages.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> EnableSabotageFlashes { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Flashes", true);
/// <summary>
/// Gets whether to enable the sabotage sound effects or not.
/// </summary>
[LocalToggleSetting]
public ConfigEntry<bool> EnableSabotageBlares { get; private set; } = config.Bind("Accessibility", "Enable Sabotage Blare", true);
}
-5
View File
@@ -64,11 +64,6 @@ public abstract class BaseModifier : IOptionable
/// </summary>
public virtual bool ShowInFreeplay => false;
/// <summary>
/// Gets a value indicating the color that should be used for the modifier within freeplay.
/// </summary>
public virtual Color FreeplayFileColor => Color.gray;
/// <summary>
/// Gets a value indicating whether the modifier is unique. If true, the player can only have one instance of this modifier.
/// </summary>
+12 -25
View File
@@ -4,10 +4,8 @@ using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.LocalSettings;
using MiraAPI.Modifiers.ModifierDisplay;
using MiraAPI.Modifiers.Types;
using MiraAPI.Patches.Roles;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using UnityEngine;
@@ -47,7 +45,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
_player = GetComponent<PlayerControl>();
if (_player == null)
{
Error("ModifierComponent could not find PlayerControl on the same GameObject.");
Logger<MiraApiPlugin>.Error("ModifierComponent could not find PlayerControl on the same GameObject.");
Destroy(this);
return;
}
@@ -77,7 +75,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
catch (Exception e)
{
Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
Logger<MiraApiPlugin>.Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
}
Modifiers.Remove(modifier);
@@ -95,7 +93,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
catch (Exception e)
{
Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
Logger<MiraApiPlugin>.Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
}
if (modifier is TimedModifier { AutoStart: true } timer)
@@ -122,24 +120,13 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
catch (Exception e)
{
Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
Logger<MiraApiPlugin>.Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
}
}
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
{
var taskPanelOpen = HudManager.Instance.TaskPanel.open;
var roleTabOpen = HudManagerPatches.RoleTab != null && HudManagerPatches.RoleTab.open;
var leftSideHud = LocalSettingsTabSingleton<MiraApiSettings>.Instance.ModifiersHudLeftSide.Value;
if (leftSideHud && (taskPanelOpen || roleTabOpen))
{
ModifierDisplay!.gameObject.SetActive(false);
}
else
{
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
}
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
}
}
@@ -153,7 +140,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
}
catch (Exception e)
{
Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
Logger<MiraApiPlugin>.Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
}
}
}
@@ -336,7 +323,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
var modifier = modifiers.FirstOrDefault();
if (modifier is null)
{
Error($"Cannot remove modifier {type.Name} because it is not active.");
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
@@ -352,7 +339,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
if (!ActiveModifiers.Contains(modifier))
{
Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
return;
}
@@ -382,7 +369,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
var modifier = GetModifier(uniqueId);
if (modifier == null)
{
Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
return;
}
@@ -412,19 +399,19 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
// TODO: Make a proper synchronization system.
if (LobbyBehaviour.Instance)
{
Warning($"Modifiers added in the lobby won't sync to new players!");
Logger<MiraApiPlugin>.Warning($"Modifiers added in the lobby won't sync to new players!");
}
var id = modifier.TypeId;
if (modifier.Unique && ActiveModifiers.Find(x => x.TypeId == id) != null)
{
Error($"Player already has modifier with id {id}!");
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {id}!");
return null;
}
if (ActiveModifiers.Contains(modifier))
{
Error($"Player already has this modifier!");
Logger<MiraApiPlugin>.Error($"Player already has this modifier!");
return null;
}
@@ -3,7 +3,6 @@ using System.Collections.ObjectModel;
using System.Linq;
using HarmonyLib;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.LocalSettings;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
@@ -95,16 +94,6 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
IsOpen = false;
_children.gameObject.SetActive(false);
if (LocalSettingsTabSingleton<MiraApiSettings>.Instance.ModifiersHudLeftSide.Value)
{
var aspect = GetComponent<AspectPosition>();
_toggleButton.transform.localPosition = new Vector3(-1f, 2.7f, 0f);
_children.transform.localPosition = new Vector3(-0.2f, -0.05f, 0f);
aspect.Alignment = AspectPosition.EdgeAlignments.LeftTop;
aspect.DistanceFromEdge = new Vector3(1.8f, 2.55f, -20f);
aspect.AdjustPosition();
}
}
/// <summary>
@@ -120,16 +109,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
/// </summary>
public void ToggleTab()
{
SetOpened(!IsOpen);
}
/// <summary>
/// Use this to set the visibility of the modifiers tab.
/// </summary>
/// <param name="val">Whether it should be visible or not.</param>
public void SetOpened(bool val)
{
IsOpen = val;
IsOpen = !IsOpen;
_children.gameObject.SetActive(IsOpen);
RefreshModifiers();
}
@@ -138,6 +118,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
{
_modifiers.Remove(component.Modifier!);
component.gameObject.DestroyImmediate();
RefreshModifiers();
}
@@ -213,7 +194,13 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
_toggleButton.gameObject.SetActive(filteredModifiers.Count != 0);
if (filteredModifiers.Count == 0)
{
_toggleButton.gameObject.SetActive(false);
return;
}
_toggleButton.gameObject.SetActive(true);
foreach (var mod in modifiersToAdd)
{
+2 -2
View File
@@ -63,7 +63,7 @@ public static class ModifierExtensions
/// </summary>
/// <param name="target">The player to remove the modifier from.</param>
/// <param name="uniqueId">The unique ID of the modifier.</param>
[MethodRpc((uint)MiraRpc.RemoveModifier)]
[MethodRpc((uint)MiraRpc.RemoveModifier, SendImmediately = true)]
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
{
target.RemoveModifier(uniqueId);
@@ -83,7 +83,7 @@ public static class ModifierExtensions
var modifier = target.GetModifier(typeId, predicate);
if (modifier is null)
{
Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
Logger<MiraApiPlugin>.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
return;
}
+5 -8
View File
@@ -5,7 +5,6 @@ using System.Runtime.Serialization;
using MiraAPI.Modifiers.Types;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random;
@@ -97,7 +96,7 @@ public static class ModifierManager
if (modifierType.GetConstructor(Type.EmptyTypes) == null)
{
Error($"Game Modifier {modifierType.FullName} does not have a parameterless constructor!");
Logger<MiraApiPlugin>.Error($"Game Modifier {modifierType.FullName} does not have a parameterless constructor!");
return false;
}
@@ -126,7 +125,6 @@ public static class ModifierManager
.Select(x => Activator.CreateInstance(x.Value) as GameModifier)
.OfType<GameModifier>()
.Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0)
.Shuffle()
.OrderByDescending(x => x.Priority())
.ToArray();
@@ -137,7 +135,7 @@ public static class ModifierManager
var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList();
if (validPlayers.Count == 0)
{
Warning($"No valid players for modifier {modifier.ModifierName}");
Logger<MiraApiPlugin>.Warning($"No valid players for modifier {modifier.ModifierName}");
continue;
}
@@ -158,7 +156,7 @@ public static class ModifierManager
if (candidates.Count == 0)
{
Warning(
Logger<MiraApiPlugin>.Warning(
$"No available players for modifier {modifier.ModifierName} at assignment {i + 1}");
break;
}
@@ -166,7 +164,7 @@ public static class ModifierManager
var plr = candidates.Random();
if (plr == null)
{
Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
Logger<MiraApiPlugin>.Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
continue;
}
@@ -179,7 +177,6 @@ public static class ModifierManager
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId) &&
modifier.CanSpawnOnCurrentMode();
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId);
}
}
-6
View File
@@ -39,10 +39,4 @@ public abstract class GameModifier : BaseModifier
/// <param name="reason">The reason why the game ended.</param>
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
public virtual bool? DidWin(GameOverReason reason) => null;
/// <summary>
/// Determines whether the modifier can spawn in general, accounting for gamemodes and everything else.
/// </summary>
/// <returns>True if the modifier is able to spawn, otherwise false.</returns>
public virtual bool CanSpawnOnCurrentMode() => !GameManager.Instance.IsHideAndSeek();
}
+1 -1
View File
@@ -80,7 +80,7 @@ public abstract class TimedModifier : BaseModifier
{
if (TimerActive)
{
Error("Can't start a timer that has already been started.");
Logger<MiraApiPlugin>.Error("Can't start a timer that has already been started.");
return;
}
+2 -2
View File
@@ -50,7 +50,7 @@ public class CustomGameOverRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<
{
if (!gameOver.VerifyCondition(innerNetObject, [.. data.Winners]))
{
Info($"Game over condition not met for {gameOver.GetType().Name}");
Logger<MiraApiPlugin>.Info($"Game over condition not met for {gameOver.GetType().Name}");
return;
}
@@ -64,7 +64,7 @@ public class CustomGameOverRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<
else
{
CustomGameOver.Instance = null;
Error($"Unknown game over reason: {data.Reason}");
Logger<MiraApiPlugin>.Error($"Unknown game over reason: {data.Reason}");
}
}
}
+9 -141
View File
@@ -1,5 +1,4 @@
using System;
using System.Collections;
using System.Collections;
using System.Linq;
using AmongUs.Data;
using AmongUs.GameOptions;
@@ -7,14 +6,10 @@ using Assets.CoreScripts;
using BepInEx.Unity.IL2CPP.Utils;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Utilities;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Rewired;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Networking;
@@ -24,7 +19,7 @@ namespace MiraAPI.Networking;
public static class CustomMurderRpc
{
/// <summary>
/// Networked Custom Murder method, ran by clients for the host to confirm kills.
/// Networked Custom Murder method.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The player to murder.</param>
@@ -34,6 +29,7 @@ public static class CustomMurderRpc
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before, SendImmediately = true)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
@@ -44,106 +40,16 @@ public static class CustomMurderRpc
bool showKillAnim = true,
bool playKillSound = true)
{
source.RpcCustomMurder(
target,
MeetingCheck.Ignore,
didSucceed,
resetKillTimer,
createDeadBody,
teleportMurderer,
showKillAnim,
playKillSound);
}
/// <summary>
/// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The player to murder.</param>
/// <param name="inMeeting">Whether the murder is intended to be triggered in a meeting.</param>
/// <param name="didSucceed">Whether the murder was successful or not.</param>
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
MeetingCheck inMeeting,
bool didSucceed = true,
bool resetKillTimer = true,
bool createDeadBody = true,
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
{
if (LobbyBehaviour.Instance)
{
return;
}
var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
var beforeMurderEvent = new BeforeMurderEvent(source, target, inMeeting);
var beforeMurderEvent = new BeforeMurderEvent(source, target);
MiraEventManager.InvokeEvent(beforeMurderEvent);
var isMeetingActive = MeetingHud.Instance != null || ExileController.Instance != null;
if ((inMeeting is MeetingCheck.ForMeeting && !isMeetingActive) || (inMeeting is MeetingCheck.OutsideMeeting && isMeetingActive))
{
beforeMurderEvent.Cancel();
}
if (beforeMurderEvent.IsCancelled)
{
murderResultFlags = MurderResultFlags.FailedError;
}
if (!PlayerControl.LocalPlayer.IsHost())
{
return;
}
RpcConfirmCustomMurder(
PlayerControl.LocalPlayer,
source,
target,
murderResultFlags,
resetKillTimer,
createDeadBody,
teleportMurderer,
showKillAnim,
playKillSound);
}
/// <summary>
/// Networked Custom Murder method, which checks for meetings as well, ran by clients for the host to confirm kills.
/// </summary>
/// <param name="host">The game host.</param>
/// <param name="source">The killer.</param>
/// <param name="target">The player to murder.</param>
/// <param name="murderResultFlags">End result for the murder.</param>
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
[MethodRpc((uint)MiraRpc.ConfirmCustomMurder, LocalHandling = RpcLocalHandling.Before)]
public static void RpcConfirmCustomMurder(
this PlayerControl host,
PlayerControl source,
PlayerControl target,
MurderResultFlags murderResultFlags,
bool resetKillTimer = true,
bool createDeadBody = true,
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
{
if (LobbyBehaviour.Instance || !host.IsHost() || target.Data.IsDead || target.Data.Disconnected)
{
return;
}
var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
source.CustomMurder(
@@ -178,7 +84,8 @@ public static class CustomMurderRpc
bool playKillSound = true)
{
source.isKilling = false;
if (resultFlags.HasFlag(MurderResultFlags.FailedError) || LobbyBehaviour.Instance)
var data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedError))
{
return;
}
@@ -262,14 +169,7 @@ public static class CustomMurderRpc
if (showKillAnim)
{
try
{
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, target.Data);
}
catch (Exception e)
{
Error($"Error with kill animation: {e.ToString()}");
}
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
}
target.cosmetics.SetNameMask(false);
@@ -301,12 +201,8 @@ public static class CustomMurderRpc
bool createDeadBody = true,
bool teleportMurderer = true)
{
if (LobbyBehaviour.Instance)
{
yield break;
}
var cam = Camera.main?.GetComponent<FollowerCamera>();
var isParticipant = source.AmOwner || target.AmOwner;
var isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
var sourcePhys = source.MyPhysics;
if (teleportMurderer)
@@ -326,7 +222,7 @@ public static class CustomMurderRpc
if (createDeadBody)
{
deadBody = Object.Instantiate(GameManager.Instance.GetDeadBody(source.Data.Role));
deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab);
deadBody.enabled = false;
deadBody.ParentId = target.PlayerId;
deadBody.bodyRenderers.ToList().ForEach(target.SetPlayerMaterialColors);
@@ -337,13 +233,6 @@ public static class CustomMurderRpc
deadBody.transform.position = vector;
}
source.Data.Role.KillAnimSpecialSetup(deadBody, source, target);
target.Data.Role.KillAnimSpecialSetup(deadBody, source, target);
// no idea if this causes bugs, but innersloth is brain-dead
// I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF
PlayerControl.LocalPlayer.Data.Role.KillAnimSpecialSetup(deadBody, source, target);
if (isParticipant)
{
if (cam != null)
@@ -392,24 +281,3 @@ public static class CustomMurderRpc
source.isKilling = false;
}
}
/// <summary>
/// Checks for custom murders.
/// </summary>
public enum MeetingCheck
{
/// <summary>
/// Checks for meetings.
/// </summary>
ForMeeting,
/// <summary>
/// Checks for a meeting to not be active.
/// </summary>
OutsideMeeting,
/// <summary>
/// Doesn't check for a meeting at all.
/// </summary>
Ignore,
}
-5
View File
@@ -35,11 +35,6 @@ public enum MiraRpc : uint
/// </summary>
CustomMurder,
/// <summary>
/// Custom Murder RPC Confirmation.
/// </summary>
ConfirmCustomMurder,
/// <summary>
/// Custom Game Over RPC.
/// </summary>
@@ -1,67 +0,0 @@
using System.Collections;
using BepInEx.Unity.IL2CPP.Utils.Collections;
using HarmonyLib;
using MiraAPI.LocalSettings;
using UnityEngine;
namespace MiraAPI.Patches.Accessibility;
[HarmonyPatch]
public static class HudManagerFlashPatches
{
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartReactorFlash))]
[HarmonyPrefix]
public static bool ReactorFlashPrefix(HudManager __instance)
{
if (__instance.ReactorFlash == null)
{
__instance.ReactorFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
}
return false;
}
[HarmonyPatch(typeof(HudManager), nameof(HudManager.StartOxyFlash))]
[HarmonyPrefix]
public static bool OxygenFlashPrefix(HudManager __instance)
{
if (__instance.OxyFlash == null)
{
__instance.OxyFlash = __instance.StartCoroutine(CoReactorFlash().WrapToIl2Cpp());
}
return false;
}
public static IEnumerator CoReactorFlash()
{
if (!HudManager.InstanceExists)
{
yield break;
}
var hudManager = HudManager.Instance;
WaitForSeconds wait = new WaitForSeconds(1f);
bool light = false;
hudManager.FullScreen.color = new Color(1f, 0f, 0f, 0.37254903f);
while (true)
{
var settins = LocalSettingsTabSingleton<MiraApiSettings>.Instance;
hudManager.FullScreen.gameObject.SetActive(settins.EnableSabotageFlashes.Value && !hudManager.FullScreen.gameObject.activeSelf);
if (settins.EnableSabotageBlares.Value)
{
SoundManager.Instance.PlaySound(ShipStatus.Instance.SabotageSound, false, 1f, null);
}
light = !light;
/*if (!MiraApiPlugin.IsMobile)
{
if (hudManager.lightFlashHandle == null)
{
hudManager.lightFlashHandle = DualshockLightManager.Instance.AllocateLight();
hudManager.lightFlashHandle.color = new Color(1f, 0f, 0f, 1f);
hudManager.lightFlashHandle.intensity = 1f;
}
hudManager.lightFlashHandle.color.SetAlpha(light ? 1f : 0f);
}*/
yield return wait;
}
}
}
-2
View File
@@ -3,12 +3,10 @@ using UnityEngine;
namespace MiraAPI.Patches;
#pragma warning disable SA1629
/// <summary>
/// Allows players to paste text into chat.
/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs
/// </summary>
#pragma warning restore SA1629
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))]
public static class ChatControllerPatch
{
@@ -1,42 +0,0 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Patches;
[HarmonyPatch(typeof(DetectiveNotesMinigame), nameof(DetectiveNotesMinigame.SetImpostorPopup))]
public static class DetectiveNotesMinigamePatch
{
public static void Prefix(DetectiveNotesMinigame __instance, bool active)
{
__instance.impostorTypePopup.gameObject.SetActive(active);
if (!active)
{
ControllerManager.Instance.CloseOverlayMenu(__instance.ImpostorOverlay);
ControllerManager.Instance.SetUpSelectables(ControllerManager.Instance.CurrentUiState, __instance.ControllerSelectable[0], __instance.ControllerSelectable);
ControllerManager.Instance.SetCurrentSelected(__instance.ControllerSelectable[2]);
return;
}
if (__instance.impostorButton != null)
{
ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, __instance.impostorTypePopup, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false);
return;
}
__instance.impostorButton = new List<GameObject>();
var allRoles = DestroyableSingleton<RoleManager>.Instance.AllRoles;
var customRoles = CustomRoleManager.CustomRoleBehaviours;
foreach (var role in allRoles)
{
if (role.TeamType != RoleTeamTypes.Impostor || role.Role == RoleTypes.ImpostorGhost || customRoles.Contains(role) )
continue;
var gameObject = Object.Instantiate(__instance.impostorTypePrefab, __instance.impostorTypeParent);
__instance.impostorButton.Add(gameObject);
gameObject.GetComponent<DetectiveImpostorType>().Initialize(__instance, role);
__instance.ImpostorIconControllerSelectables.Add(gameObject.GetComponent<PassiveButton>());
}
ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, __instance.impostorTypePopup, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false);
}
}
+5 -7
View File
@@ -12,16 +12,14 @@ public static class CanUsePatches
public static IEnumerable<MethodBase> TargetMethods()
{
yield return AccessTools.Method(typeof(Console), nameof(Console.CanUse));
yield return AccessTools.Method(typeof(DeconControl), nameof(DeconControl.CanUse));
yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.CanUse));
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse));
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.CanUse));
yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse));
yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.CanUse));
// vent is not necessary as it has its own patch
}
[HarmonyPriority(Priority.Last)]
+1 -1
View File
@@ -8,7 +8,7 @@ namespace MiraAPI.Patches.Events;
internal static class EndGamePatches
{
[HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
public static class GameManagerRpcEndGamePatch
public static class GameManagerPatches
{
public static bool Prefix(GameOverReason endReason)
{
@@ -1,24 +0,0 @@
using System.Reflection;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Utilities;
namespace MiraAPI.Patches.Events;
[HarmonyPatch]
public static class FreeplayRoundStartPatch
{
public static MethodBase TargetMethod()
{
return Helpers.GetStateMachineMoveNext<TutorialManager>(nameof(TutorialManager.RunTutorial))!;
}
public static void Postfix(ref bool __result)
{
if (!__result)
{
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
}
}
}
@@ -1,17 +0,0 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Player;
namespace MiraAPI.Patches.Events;
[HarmonyPatch(typeof(HideAndSeekManager))]
public static class HnsFinishTaskPatch
{
[HarmonyPostfix]
[HarmonyPatch(nameof(HideAndSeekManager.FinishTask))]
public static void FinishTaskPostfix(HideAndSeekManager __instance, PlayerTask task)
{
var completeTaskEvent = new CompleteHnsTaskEvent(__instance, task.Owner, task);
MiraEventManager.InvokeEvent(completeTaskEvent);
}
}
@@ -1,35 +0,0 @@
using System.Reflection;
using HarmonyLib;
using Il2CppInterop.Runtime.InteropTypes;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Utilities;
namespace MiraAPI.Patches.Events;
/// <summary>
/// Patch to invoke AfterMurderEvent after a kill animation is performed.
/// </summary>
[HarmonyPatch]
public static class KillAnimationMurderEventPatch
{
public static MethodBase TargetMethod()
{
return Helpers.GetStateMachineMoveNext<KillAnimation>(nameof(KillAnimation.CoPerformKill))!;
}
public static void Postfix(Il2CppObjectBase __instance)
{
var wrapper = new StateMachineWrapper<KillAnimation>(__instance);
if (wrapper.GetState() != 1)
{
return;
}
var source = wrapper.GetParameter<PlayerControl>("source");
var target = wrapper.GetParameter<PlayerControl>("target");
var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId));
MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
@@ -0,0 +1,30 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Utilities;
namespace MiraAPI.Patches.Events;
/// <summary>
/// General patches for the KillAnimation class.
/// </summary>
[HarmonyPatch]
public static class KillAnimationPatches
{
/// <summary>
/// Used to trigger the <see cref="AfterMurderEvent"/>.
/// </summary>
[HarmonyPostfix]
[HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
{
// Checking if state is -1 to wait for the end of coroutine.
if (__instance.__1__state != -1)
{
return;
}
var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId));
MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
@@ -41,7 +41,7 @@ public static class ReportBodyEventPatch
var source = AmongUsClient.Instance.FindObjectByNetId<PlayerControl>(reportBodyMessage.rpcObjectNetId);
if (source == null)
{
Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
Logger<MiraApiPlugin>.Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
return true;
}
@@ -1,5 +1,4 @@
/*
using HarmonyLib;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
@@ -10,13 +9,7 @@ internal static class SpawnInMinigamePatch
{
public static void Postfix()
{
var @event = new BeforeRoundStartEvent(false);
var @event = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
if (@event.IsCancelled) return;
var @event2 = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event2);
}
}
*/
@@ -1,5 +1,4 @@
/*
using System.Collections.Generic;
using System.Collections.Generic;
using System.Reflection;
using HarmonyLib;
using MiraAPI.Events;
@@ -26,13 +25,7 @@ internal static class SubmergedSpawnGamePatch
public static void Postfix()
{
var @event = new BeforeRoundStartEvent(false);
var @event = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event);
if (@event.IsCancelled) return;
var @event2 = new RoundStartEvent(false);
MiraEventManager.InvokeEvent(@event2);
}
}
*/
@@ -1,75 +0,0 @@
using System.Linq;
using AmongUs.GameOptions;
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Events.Vanilla.Usables;
namespace MiraAPI.Patches.Events;
/// <summary>
/// Patches to invoke vent related MiraEvents.
/// </summary>
[HarmonyPatch]
public static class VanillaButtonPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(AbilityButton), nameof(AbilityButton.DoClick))]
public static bool DoClickPrefix(AbilityButton __instance)
{
// Invoke the generic button click event.
PlayerControl? playerTarget = null;
Vent? ventTarget = null;
var role = PlayerControl.LocalPlayer.Data.Role;
if (role.Role is RoleTypes.Tracker)
{
playerTarget = role.Cast<TrackerRole>().currentTarget;
}
else if (role.Role is RoleTypes.GuardianAngel)
{
playerTarget = role.Cast<GuardianAngelRole>().currentTarget;
}
else if (role.Role is RoleTypes.Engineer)
{
ventTarget = role.Cast<EngineerRole>().currentTarget;
}
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget, ventTarget);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
}
if (!genericEvent.IsCancelled)
{
return true;
}
return false;
}
[HarmonyPrefix]
[HarmonyPatch(typeof(SecondaryAbilityButton), nameof(SecondaryAbilityButton.DoClick))]
public static bool SecondaryDoClickPrefix(AbilityButton __instance)
{
// Invoke the generic button click event.
PlayerControl? playerTarget = null;
var role = PlayerControl.LocalPlayer.Data.Role;
if (role.Role is RoleTypes.Detective)
{
playerTarget = role.Cast<DetectiveRole>().currentTarget;
}
var genericEvent = new VanillaButtonClickEvent(__instance, playerTarget);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new VanillaButtonCancelledEvent(__instance));
}
if (!genericEvent.IsCancelled)
{
return true;
}
return false;
}
}
@@ -1,5 +1,7 @@
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Patches.Freeplay;
@@ -20,4 +22,15 @@ internal static class TaskAddButtonPatches
return true;
}
[HarmonyPostfix]
[HarmonyPatch(nameof(TaskAddButton.Role), MethodType.Setter)]
public static void RoleGetterPatch(TaskAddButton __instance)
{
if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
{
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
}
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
}
Loaded 100 of 170 files, more files were not shown because too many files have changed in this diff. Show more