mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-21 13:37:54 +00:00
Compare commits
94
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aa4a82d48 | ||
|
|
da7aab9aa2 | ||
|
|
3d274c2f61 | ||
|
|
b94e20c6f7 | ||
|
|
5a0428c7c0 | ||
|
|
c41dd27f88 | ||
|
|
6872ca6866 | ||
|
|
4b7fa906b9 | ||
|
|
b7173d18ab | ||
|
|
ffc7a1a4fd | ||
|
|
a1611f5f7a | ||
|
|
1664aa1003 | ||
|
|
af3d99eafe | ||
|
|
94197e38dd | ||
|
|
97a0b2084a | ||
|
|
1d87790212 | ||
|
|
9d130a53de | ||
|
|
d8afc6887d | ||
|
|
c0a406610d | ||
|
|
d73c21936c | ||
|
|
449192f0ef | ||
|
|
f3614b9a5c | ||
|
|
a62c6d496c | ||
|
|
bebd648691 | ||
|
|
ecadfa86dd | ||
|
|
5ad53f695f | ||
|
|
31a9c0c98d | ||
|
|
ea389b986f | ||
|
|
ef85fab6a6 | ||
|
|
b2fdaaf89f | ||
|
|
e6043b6f5f | ||
|
|
ffe0db0e9e | ||
|
|
705ef64b4d | ||
|
|
ebdb4a09ca | ||
|
|
ac113b6a7f | ||
|
|
1f6962efe9 | ||
|
|
6b37b9c09c | ||
|
|
f515516937 | ||
|
|
8b2d24ccc1 | ||
|
|
57bab258e5 | ||
|
|
04d5e72caa | ||
|
|
b4e6e448f2 | ||
|
|
b19a0178ad | ||
|
|
8a71f13874 | ||
|
|
809e7a07ab | ||
|
|
47394c6afe | ||
|
|
a2a1e91439 | ||
|
|
2f5ca32475 | ||
|
|
5fb59408fa | ||
|
|
57c842ece6 | ||
|
|
63e552245d | ||
|
|
43d7f80660 | ||
|
|
45291b0e98 | ||
|
|
45972f71a9 | ||
|
|
a6f7171b71 | ||
|
|
e6d3819517 | ||
|
|
b97ada22bc | ||
|
|
5f67960b28 | ||
|
|
b4e30b2b68 | ||
|
|
2237d3a2df | ||
|
|
e708c23f81 | ||
|
|
3ca33b1d86 | ||
|
|
8967348cd3 | ||
|
|
305d2c3714 | ||
|
|
85a0275eea | ||
|
|
f006deab3f | ||
|
|
a274f23e65 | ||
|
|
f1a5aef581 | ||
|
|
ce15e93e59 | ||
|
|
08ca995eb1 | ||
|
|
453259df98 | ||
|
|
2ec3eec370 | ||
|
|
2ee8730ba4 | ||
|
|
c89e5938cc | ||
|
|
513be01c02 | ||
|
|
01fa1f0915 | ||
|
|
355eef509b | ||
|
|
763a493283 | ||
|
|
68e421bf91 | ||
|
|
c343ab5e9f | ||
|
|
9c1ef70ea7 | ||
|
|
c0c8fa08ff | ||
|
|
e7fba386aa | ||
|
|
72e5e0c137 | ||
|
|
4422072060 | ||
|
|
8145c8e599 | ||
|
|
afd6fbc3b6 | ||
|
|
74741b41a7 | ||
|
|
f98429b5f9 | ||
|
|
dfe124d67a | ||
|
|
2b06633488 | ||
|
|
4784b1f0ad | ||
|
|
d9d0d6bc62 | ||
|
|
cd818a18cf |
No files matched your search
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||||
|
|
||||||
<VersionPrefix>0.2.0</VersionPrefix>
|
<VersionPrefix>0.2.1</VersionPrefix>
|
||||||
<VersionSuffix>dev</VersionSuffix>
|
<VersionSuffix>dev</VersionSuffix>
|
||||||
|
|
||||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles;
|
|||||||
using MiraAPI.Example.Roles;
|
using MiraAPI.Example.Roles;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
@@ -15,10 +16,10 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
|||||||
public override string Name => "Freeze";
|
public override string Name => "Freeze";
|
||||||
|
|
||||||
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
|
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
|
||||||
|
public override bool PauseTimerInVent => true;
|
||||||
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
|
|
||||||
|
|
||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||||
|
public override MiraKeybind? Keybind => MiraGlobalKeybinds.PrimaryAbility;
|
||||||
|
|
||||||
protected override void OnClick()
|
protected override void OnClick()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using MiraAPI.Example.Modifiers;
|
using MiraAPI.Example.Modifiers;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -15,6 +16,7 @@ public class MeetingButton : CustomActionButton
|
|||||||
public override int MaxUses => 3;
|
public override int MaxUses => 3;
|
||||||
|
|
||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||||
|
public override MiraKeybind? Keybind => MiraGlobalKeybinds.ModifierPrimaryAbility;
|
||||||
|
|
||||||
public override bool Enabled(RoleBehaviour? role)
|
public override bool Enabled(RoleBehaviour? role)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using MiraAPI.Example.Roles;
|
using MiraAPI.Example.Roles;
|
||||||
using MiraAPI.GameEnd;
|
using MiraAPI.GameEnd;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -12,6 +13,8 @@ public class NeutralKillerButton : CustomActionButton
|
|||||||
public override string Name => "Win Game";
|
public override string Name => "Win Game";
|
||||||
public override float Cooldown => 0f;
|
public override float Cooldown => 0f;
|
||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||||
|
public override MiraKeybind Keybind => ExampleKeybinds.NeutralWinKeybind;
|
||||||
|
|
||||||
protected override void OnClick()
|
protected override void OnClick()
|
||||||
{
|
{
|
||||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles;
|
|||||||
using MiraAPI.Example.Roles;
|
using MiraAPI.Example.Roles;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -21,6 +22,7 @@ public class TeleportButton : CustomActionButton
|
|||||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
||||||
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
||||||
public static bool IsZoom { get; private set; }
|
public static bool IsZoom { get; private set; }
|
||||||
|
public override MiraKeybind? Keybind => MiraGlobalKeybinds.PrimaryAbility;
|
||||||
|
|
||||||
public override bool Enabled(RoleBehaviour? role)
|
public override bool Enabled(RoleBehaviour? role)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
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]);
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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,4 +1,5 @@
|
|||||||
using BepInEx;
|
using System.Linq;
|
||||||
|
using BepInEx;
|
||||||
using BepInEx.Configuration;
|
using BepInEx.Configuration;
|
||||||
using BepInEx.Unity.IL2CPP;
|
using BepInEx.Unity.IL2CPP;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
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;
|
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp.
|
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp if the event isn't cancelled.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RoundStartEvent : MiraEvent
|
public class RoundStartEvent : MiraEvent
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
@@ -2,9 +2,14 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
using MiraAPI.Events.Mira;
|
using MiraAPI.Events.Mira;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
using MiraAPI.Patches;
|
using MiraAPI.Patches;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
using Rewired;
|
||||||
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.Events;
|
using UnityEngine.Events;
|
||||||
using UnityEngine.UI;
|
using UnityEngine.UI;
|
||||||
@@ -32,6 +37,12 @@ public abstract class CustomActionButton
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract float Cooldown { get; }
|
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>
|
/// <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.
|
/// 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>
|
/// </summary>
|
||||||
@@ -43,7 +54,12 @@ public abstract class CustomActionButton
|
|||||||
public virtual string CooldownTimerFormatString => "0";
|
public virtual string CooldownTimerFormatString => "0";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
|
/// 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.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual float EffectDuration => 0;
|
public virtual float EffectDuration => 0;
|
||||||
|
|
||||||
@@ -52,6 +68,16 @@ public abstract class CustomActionButton
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual int MaxUses => 0;
|
public virtual int MaxUses => 0;
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the button's text outline color.
|
/// Gets the button's text outline color.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -62,11 +88,6 @@ public abstract class CustomActionButton
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
|
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets a value indicating whether the button has an effect ability.
|
|
||||||
/// </summary>
|
|
||||||
public bool HasEffect => EffectDuration > 0;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether the button has limited uses.
|
/// Gets a value indicating whether the button has limited uses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -97,6 +118,16 @@ public abstract class CustomActionButton
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ActionButton? Button { get; set; }
|
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>
|
/// <summary>
|
||||||
/// The method used to create the button.
|
/// The method used to create the button.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -168,6 +199,26 @@ public abstract class CustomActionButton
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (Keybind != null)
|
||||||
|
{
|
||||||
|
Keybind.OnActivate(() =>
|
||||||
|
{
|
||||||
|
if (Enabled(PlayerControl.LocalPlayer.Data.Role))
|
||||||
|
{
|
||||||
|
ClickHandler();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
KeybindIcon =
|
||||||
|
Helpers.CreateKeybindIcon(
|
||||||
|
Button.gameObject,
|
||||||
|
Keybind.CurrentKey,
|
||||||
|
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
|
||||||
|
);
|
||||||
|
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||||
|
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -354,6 +405,13 @@ 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>
|
/// <summary>
|
||||||
/// When the button is usable, this method is called to determine if the button can be clicked.
|
/// 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.
|
/// By default, it takes into account the timer, effect, and uses.
|
||||||
@@ -362,7 +420,7 @@ public abstract class CustomActionButton
|
|||||||
/// <returns>A value that represents whether the button can be clicked or not.</returns>
|
/// <returns>A value that represents whether the button can be clicked or not.</returns>
|
||||||
public virtual bool CanClick()
|
public virtual bool CanClick()
|
||||||
{
|
{
|
||||||
return Timer <= 0 && !EffectActive && CanUse();
|
return (EffectActive ? IsEffectCancellable() : Timer <= 0) && CanUse();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -372,7 +430,8 @@ public abstract class CustomActionButton
|
|||||||
/// <returns>A value that represents whether the button should light up or not.</returns>
|
/// <returns>A value that represents whether the button should light up or not.</returns>
|
||||||
public virtual bool CanUse()
|
public virtual bool CanUse()
|
||||||
{
|
{
|
||||||
return PlayerControl.LocalPlayer.moveable && (!LimitedUses || UsesLeft > 0);
|
return PlayerControl.LocalPlayer.moveable &&
|
||||||
|
((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -394,6 +453,12 @@ public abstract class CustomActionButton
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public virtual void ClickHandler()
|
public virtual void ClickHandler()
|
||||||
{
|
{
|
||||||
|
if (EffectActive && IsEffectCancellable())
|
||||||
|
{
|
||||||
|
ResetCooldownAndOrEffect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!CanClick())
|
if (!CanClick())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -426,14 +491,22 @@ public abstract class CustomActionButton
|
|||||||
/// <param name="playerControl">The local PlayerControl.</param>
|
/// <param name="playerControl">The local PlayerControl.</param>
|
||||||
public virtual void FixedUpdateHandler(PlayerControl playerControl)
|
public virtual void FixedUpdateHandler(PlayerControl playerControl)
|
||||||
{
|
{
|
||||||
|
if (Keybind != null && KeybindText != null)
|
||||||
|
{
|
||||||
|
KeybindText.text = Keybind.CurrentKey.ToString();
|
||||||
|
KeybindIcon?.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||||
|
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value &&
|
||||||
|
Keybind.CurrentKey != KeyboardKeyCode.None);
|
||||||
|
}
|
||||||
|
|
||||||
if (Timer >= 0)
|
if (Timer >= 0)
|
||||||
{
|
{
|
||||||
if (!TimerPaused)
|
if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent))
|
||||||
{
|
{
|
||||||
Timer -= Time.deltaTime;
|
Timer -= Time.deltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (HasEffect && EffectActive)
|
else if (HasEffect && EffectActive && EffectDuration > 0)
|
||||||
{
|
{
|
||||||
EffectActive = false;
|
EffectActive = false;
|
||||||
Timer = Cooldown;
|
Timer = Cooldown;
|
||||||
@@ -451,7 +524,7 @@ public abstract class CustomActionButton
|
|||||||
Button!.SetDisabled();
|
Button!.SetDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EffectActive)
|
if (EffectActive && EffectDuration > 0)
|
||||||
{
|
{
|
||||||
Button.SetFillUp(Timer, EffectDuration);
|
Button.SetFillUp(Timer, EffectDuration);
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using MiraAPI.Events.Mira;
|
using MiraAPI.Events.Mira;
|
||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Hud;
|
namespace MiraAPI.Hud;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
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 = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
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 KeybindManager.Keybinds)
|
||||||
|
{
|
||||||
|
if (instance.userData.actions.ToArray().Any(x => x.name == keybind.Id))
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.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)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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})";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace MiraAPI.Keybinds;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to mark a class for custom keybinds registration.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
public class RegisterCustomKeybindsAttribute : Attribute;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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?.name;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
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/>
|
||||||
|
internal override LocalEnumSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||||
|
{
|
||||||
|
return new LocalEnumSetting(tab, configEntryBase, configEntryBase.SettingType, _name, _description, names);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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/>
|
||||||
|
internal override LocalNumberSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||||
|
{
|
||||||
|
return new LocalNumberSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), increment, suffixType, formatString);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
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>
|
||||||
|
internal abstract ILocalSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
using System;
|
||||||
|
using BepInEx.Configuration;
|
||||||
|
|
||||||
|
namespace MiraAPI.LocalSettings.Attributes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Attribute for local setting buttons.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Property)]
|
||||||
|
public class LocalSettingsButtonAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
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/>
|
||||||
|
internal override LocalSliderSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||||
|
{
|
||||||
|
return new LocalSliderSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), displayValue, suffixType, formatString, roundValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
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/>
|
||||||
|
internal override LocalToggleSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||||
|
{
|
||||||
|
return new LocalToggleSetting(tab, configEntryBase, _name, _description);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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))
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}));
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MiraAPI.LocalSettings;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Struct for modyfing tab apparance.
|
||||||
|
/// 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 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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,132 @@
|
|||||||
|
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>";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
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>";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
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; }
|
||||||
|
|
||||||
|
internal 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, casted 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
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>";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,88 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
using BepInEx.Configuration;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
|
using MiraAPI.LocalSettings.Attributes;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <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("Displays", "Show Modifiers HUD on Left Side", false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether vents and their connections appear on the map.
|
||||||
|
/// </summary>
|
||||||
|
[LocalToggleSetting]
|
||||||
|
public ConfigEntry<bool> ShowVentsOnMap { get; private set; } = config.Bind("Displays", "Show Vent Layout on Map", true);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether cooldowns for buttons below 10 seconds appear with decimals.
|
||||||
|
/// </summary>
|
||||||
|
[LocalToggleSetting]
|
||||||
|
public ConfigEntry<bool> PreciseCooldowns { get; private set; } = config.Bind("Buttons", "Precise Button Cooldowns Under 10s", false);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the size of ability buttons in-game.
|
||||||
|
/// </summary>
|
||||||
|
[LocalSliderSetting(min: 0.5f, max: 1.5f, displayValue: true, formatString: "0.00", suffixType: MiraNumberSuffixes.Multiplier)]
|
||||||
|
public ConfigEntry<float> ButtonUiScale { get; private set; } = config.Bind("Buttons", "Button Scale Factor", 0.8f);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets whether to show keybinds in the control mapper.
|
||||||
|
/// </summary>
|
||||||
|
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds in Control Mapper", true);
|
||||||
|
}
|
||||||
@@ -4,8 +4,10 @@ using System.Collections.Immutable;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Il2CppInterop.Runtime.Attributes;
|
using Il2CppInterop.Runtime.Attributes;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
using MiraAPI.Modifiers.ModifierDisplay;
|
using MiraAPI.Modifiers.ModifierDisplay;
|
||||||
using MiraAPI.Modifiers.Types;
|
using MiraAPI.Modifiers.Types;
|
||||||
|
using MiraAPI.Patches.Roles;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using Reactor.Utilities.Attributes;
|
using Reactor.Utilities.Attributes;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
@@ -126,7 +128,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
|
|
||||||
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
|
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
|
||||||
{
|
{
|
||||||
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using Il2CppInterop.Runtime.Attributes;
|
using Il2CppInterop.Runtime.Attributes;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using Reactor.Utilities.Attributes;
|
using Reactor.Utilities.Attributes;
|
||||||
using Reactor.Utilities.Extensions;
|
using Reactor.Utilities.Extensions;
|
||||||
@@ -94,6 +95,16 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
|
|
||||||
IsOpen = false;
|
IsOpen = false;
|
||||||
_children.gameObject.SetActive(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>
|
/// <summary>
|
||||||
@@ -109,7 +120,16 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void ToggleTab()
|
public void ToggleTab()
|
||||||
{
|
{
|
||||||
IsOpen = !IsOpen;
|
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;
|
||||||
_children.gameObject.SetActive(IsOpen);
|
_children.gameObject.SetActive(IsOpen);
|
||||||
RefreshModifiers();
|
RefreshModifiers();
|
||||||
}
|
}
|
||||||
@@ -118,7 +138,6 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
{
|
{
|
||||||
_modifiers.Remove(component.Modifier!);
|
_modifiers.Remove(component.Modifier!);
|
||||||
component.gameObject.DestroyImmediate();
|
component.gameObject.DestroyImmediate();
|
||||||
|
|
||||||
RefreshModifiers();
|
RefreshModifiers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,13 +213,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
|||||||
|
|
||||||
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
|
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
|
||||||
|
|
||||||
if (filteredModifiers.Count == 0)
|
_toggleButton.gameObject.SetActive(filteredModifiers.Count != 0);
|
||||||
{
|
|
||||||
_toggleButton.gameObject.SetActive(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_toggleButton.gameObject.SetActive(true);
|
|
||||||
|
|
||||||
foreach (var mod in modifiersToAdd)
|
foreach (var mod in modifiersToAdd)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ public static class ModifierExtensions
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="target">The player to remove the modifier from.</param>
|
/// <param name="target">The player to remove the modifier from.</param>
|
||||||
/// <param name="uniqueId">The unique ID of the modifier.</param>
|
/// <param name="uniqueId">The unique ID of the modifier.</param>
|
||||||
[MethodRpc((uint)MiraRpc.RemoveModifier, SendImmediately = true)]
|
[MethodRpc((uint)MiraRpc.RemoveModifier)]
|
||||||
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
|
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
|
||||||
{
|
{
|
||||||
target.RemoveModifier(uniqueId);
|
target.RemoveModifier(uniqueId);
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ public static class CustomMurderRpc
|
|||||||
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</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="showKillAnim">Should the kill animation be shown.</param>
|
||||||
/// <param name="playKillSound">Should the kill sound be played.</param>
|
/// <param name="playKillSound">Should the kill sound be played.</param>
|
||||||
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before, SendImmediately = true)]
|
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
|
||||||
public static void RpcCustomMurder(
|
public static void RpcCustomMurder(
|
||||||
this PlayerControl source,
|
this PlayerControl source,
|
||||||
PlayerControl target,
|
PlayerControl target,
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Events;
|
||||||
|
using MiraAPI.Events.Vanilla.Gameplay;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.Events;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(TutorialManager._RunTutorial_d__3), nameof(TutorialManager._RunTutorial_d__3.MoveNext))]
|
||||||
|
public static class FreeplayRoundStartPatch
|
||||||
|
{
|
||||||
|
public static void Postfix(TutorialManager._RunTutorial_d__3 __instance, ref bool __result)
|
||||||
|
{
|
||||||
|
if (!__result)
|
||||||
|
{
|
||||||
|
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,7 +9,12 @@ internal static class SpawnInMinigamePatch
|
|||||||
{
|
{
|
||||||
public static void Postfix()
|
public static void Postfix()
|
||||||
{
|
{
|
||||||
var @event = new RoundStartEvent(false);
|
var @event = new BeforeRoundStartEvent(false);
|
||||||
MiraEventManager.InvokeEvent(@event);
|
MiraEventManager.InvokeEvent(@event);
|
||||||
|
|
||||||
|
if (@event.IsCancelled) return;
|
||||||
|
|
||||||
|
var @event2 = new RoundStartEvent(false);
|
||||||
|
MiraEventManager.InvokeEvent(@event2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,12 @@ internal static class SubmergedSpawnGamePatch
|
|||||||
|
|
||||||
public static void Postfix()
|
public static void Postfix()
|
||||||
{
|
{
|
||||||
var @event = new RoundStartEvent(false);
|
var @event = new BeforeRoundStartEvent(false);
|
||||||
MiraEventManager.InvokeEvent(@event);
|
MiraEventManager.InvokeEvent(@event);
|
||||||
|
|
||||||
|
if (@event.IsCancelled) return;
|
||||||
|
|
||||||
|
var @event2 = new RoundStartEvent(false);
|
||||||
|
MiraEventManager.InvokeEvent(@event2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using AmongUs.GameOptions;
|
using AmongUs.GameOptions;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
|
using MiraAPI.Modifiers.Types;
|
||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
@@ -336,6 +337,7 @@ internal static class TaskAdderPatches
|
|||||||
taskAddButton.MyTask = null;
|
taskAddButton.MyTask = null;
|
||||||
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
|
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
|
||||||
taskAddButton.Text.text = modifier.ModifierName;
|
taskAddButton.Text.text = modifier.ModifierName;
|
||||||
|
taskAddButton.Text.fontSizeMin = 1;
|
||||||
taskAddButton.Text.EnableMasking();
|
taskAddButton.Text.EnableMasking();
|
||||||
taskAddButton.FileImage.color = modifier.FreeplayFileColor;
|
taskAddButton.FileImage.color = modifier.FreeplayFileColor;
|
||||||
taskAddButton.RolloverHandler.OutColor = modifier.FreeplayFileColor;
|
taskAddButton.RolloverHandler.OutColor = modifier.FreeplayFileColor;
|
||||||
@@ -355,6 +357,12 @@ internal static class TaskAdderPatches
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (modifier is TimedModifier timed)
|
||||||
|
{
|
||||||
|
taskAddButton.FileImage.sprite = MiraAssets.TimedModifierFile.LoadAsset();
|
||||||
|
taskAddButton.Text.text += $"({timed.Duration}s)";
|
||||||
|
}
|
||||||
|
|
||||||
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
|
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
|
||||||
|
|
||||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
|
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
|
||||||
|
|||||||
@@ -20,6 +20,11 @@ public static class ButtonResetPatches
|
|||||||
{
|
{
|
||||||
Logger<MiraApiPlugin>.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
|
Logger<MiraApiPlugin>.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (customActionButton.UsesMode == ButtonUsesMode.PerRound)
|
||||||
|
{
|
||||||
|
customActionButton.SetUses(customActionButton.MaxUses);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,14 @@
|
|||||||
using HarmonyLib;
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using Reactor.Utilities.Extensions;
|
using Reactor.Utilities.Extensions;
|
||||||
|
using Rewired;
|
||||||
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
@@ -18,6 +25,8 @@ public static class HudManagerPatches
|
|||||||
public static Transform? BottomRight { get; private set; }
|
public static Transform? BottomRight { get; private set; }
|
||||||
public static Transform? Buttons { get; private set; }
|
public static Transform? Buttons { get; private set; }
|
||||||
|
|
||||||
|
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Trigger hudstart on current custom gamemode
|
/// Trigger hudstart on current custom gamemode
|
||||||
@@ -90,8 +99,35 @@ public static class HudManagerPatches
|
|||||||
|
|
||||||
gridArrange.Start();
|
gridArrange.Start();
|
||||||
gridArrange.ArrangeChilds();
|
gridArrange.ArrangeChilds();
|
||||||
|
|
||||||
aspectPosition.AdjustPosition();
|
aspectPosition.AdjustPosition();
|
||||||
|
|
||||||
|
vanillaKeybindIcons = [];
|
||||||
|
var keybindIconPos = new Vector3(-0.4f, 0.4f, -9.5f);
|
||||||
|
var vanillaButtons = new Dictionary<GameObject, int>
|
||||||
|
{
|
||||||
|
{ __instance.KillButton.gameObject, 8 },
|
||||||
|
{ __instance.UseButton.gameObject, 6 },
|
||||||
|
{ __instance.ReportButton.gameObject, 7 },
|
||||||
|
{ __instance.ImpostorVentButton.gameObject, 50 },
|
||||||
|
{ __instance.SabotageButton.gameObject, 4 },
|
||||||
|
{ __instance.AbilityButton.gameObject, 49 },
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var kvp in vanillaButtons)
|
||||||
|
{
|
||||||
|
var buttonObj = kvp.Key;
|
||||||
|
var actionId = kvp.Value;
|
||||||
|
|
||||||
|
var key = KeybindUtils.GetKeycodeByActionId(actionId);
|
||||||
|
if (key == KeyboardKeyCode.None)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos);
|
||||||
|
vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent<TextMeshPro>(), actionId);
|
||||||
|
var comp = buttonObj.GetComponent<ActionButton>();
|
||||||
|
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -123,4 +159,35 @@ public static class HudManagerPatches
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HarmonyPatch(nameof(HudManager.Update))]
|
||||||
|
[HarmonyPostfix]
|
||||||
|
public static void UpdatePostfix()
|
||||||
|
{
|
||||||
|
foreach (var btnIcon in vanillaKeybindIcons)
|
||||||
|
{
|
||||||
|
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
|
||||||
|
btnIcon.Key.transform.parent.gameObject.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||||
|
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
var player = ReInput.players.GetPlayer(0);
|
||||||
|
foreach (var entry in KeybindManager.Keybinds)
|
||||||
|
{
|
||||||
|
var keyboard = player.controllers.Keyboard;
|
||||||
|
bool modifierKeysPressed = entry.ModifierKeys.All(k => keyboard.GetModifierKey(k)) ||
|
||||||
|
entry.ModifierKeys.Length <= 0;
|
||||||
|
if (player.GetButtonDown(entry.Id) && modifierKeysPressed)
|
||||||
|
{
|
||||||
|
entry.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var entry in KeybindManager.VanillaKeybinds.Values)
|
||||||
|
{
|
||||||
|
if (player.GetButtonDown(entry.Id))
|
||||||
|
{
|
||||||
|
entry.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,16 @@ public static class IntroCutscenePatches
|
|||||||
MiraEventManager.InvokeEvent(@event);
|
MiraEventManager.InvokeEvent(@event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(typeof(IntroCutscene._ShowRole_d__41), nameof(IntroCutscene._ShowRole_d__41.MoveNext))]
|
||||||
|
[HarmonyPriority(Priority.Last)]
|
||||||
|
public static void ShowRolePatch(IntroCutscene._ShowRole_d__41 __instance)
|
||||||
|
{
|
||||||
|
var realInstance = __instance.__4__this;
|
||||||
|
var @event = new IntroRoleRevealEvent(realInstance);
|
||||||
|
MiraEventManager.InvokeEvent(@event);
|
||||||
|
}
|
||||||
|
|
||||||
[HarmonyPrefix]
|
[HarmonyPrefix]
|
||||||
[HarmonyPatch(nameof(IntroCutscene.BeginImpostor))]
|
[HarmonyPatch(nameof(IntroCutscene.BeginImpostor))]
|
||||||
[HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))]
|
[HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))]
|
||||||
@@ -61,6 +71,11 @@ public static class IntroCutscenePatches
|
|||||||
public static void GameBeginPatch(IntroCutscene __instance)
|
public static void GameBeginPatch(IntroCutscene __instance)
|
||||||
{
|
{
|
||||||
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
|
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
|
||||||
|
|
||||||
|
var @event = new BeforeRoundStartEvent(true);
|
||||||
|
MiraEventManager.InvokeEvent(@event);
|
||||||
|
|
||||||
|
if (@event.IsCancelled) return;
|
||||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
using Reactor.Utilities.Extensions;
|
||||||
|
using Rewired.UI.ControlMapper;
|
||||||
|
using TMPro;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.Keybinds;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(ControlMapper))]
|
||||||
|
public static class ControlMapperPatches
|
||||||
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(ControlMapper.Start))]
|
||||||
|
private static void StartPrefix(ControlMapper __instance)
|
||||||
|
{
|
||||||
|
var doneButton = GameObject.Find("DoneButton")?.GetComponent<CustomButton>();
|
||||||
|
if (doneButton == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var toggle = Object.Instantiate(doneButton, doneButton.transform.parent);
|
||||||
|
var text = toggle.GetComponentInChildren<TextMeshProUGUI>();
|
||||||
|
var entry = LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds;
|
||||||
|
toggle.gameObject.name = "KeybindsVisibleToggle";
|
||||||
|
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
|
||||||
|
toggle.onClick.RemoveAllListeners();
|
||||||
|
toggle.GetComponent<ButtonInfo>().identifier = "KeybindsVisibleToggle";
|
||||||
|
toggle.onClick.AddListener((UnityAction)(() =>
|
||||||
|
{
|
||||||
|
entry.Value = !entry.Value;
|
||||||
|
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(ControlMapper.Update))]
|
||||||
|
private static void UpdatePrefix(ControlMapper __instance)
|
||||||
|
{
|
||||||
|
foreach (var element in __instance.themedElements)
|
||||||
|
{
|
||||||
|
var info = element.GetComponent<InputFieldInfo>();
|
||||||
|
if (info == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var key = info.glyphOrText?.actionElementMap?.keyboardKeyCode;
|
||||||
|
if (key == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var conflicts = KeybindManager.GetConflicts();
|
||||||
|
if (conflicts.Any(x => x.Key.ToString() == key.ToString()))
|
||||||
|
{
|
||||||
|
element.GetComponent<Image>().color = Color.red;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using MonoMod.Utils;
|
||||||
|
using Rewired;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.Keybinds;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(InputManager_Base), nameof(InputManager_Base.Awake))]
|
||||||
|
public static class KeybindMenuPatch
|
||||||
|
{
|
||||||
|
private static bool _registered;
|
||||||
|
|
||||||
|
[HarmonyPrefix]
|
||||||
|
private static void AwakePrefix(InputManager_Base __instance)
|
||||||
|
{
|
||||||
|
if (_registered)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeybindUtils.RewiredInputManager = __instance;
|
||||||
|
KeybindManager.RewiredInit();
|
||||||
|
_registered = true;
|
||||||
|
|
||||||
|
// Set these here right after input manager initializes
|
||||||
|
KeybindManager.VanillaKeybinds = new()
|
||||||
|
{
|
||||||
|
{ typeof(KillButton), new VanillaKeybind(8) },
|
||||||
|
{ typeof(UseButton), new VanillaKeybind(6) },
|
||||||
|
{ typeof(ReportButton), new VanillaKeybind(7) },
|
||||||
|
{ typeof(VentButton), new VanillaKeybind(50) },
|
||||||
|
{ typeof(SabotageButton), new VanillaKeybind(4) },
|
||||||
|
{ typeof(AbilityButton), new VanillaKeybind(49) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.Keybinds;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(OptionsMenuBehaviour))]
|
||||||
|
public static class OptionsMenuBehaviourPatch
|
||||||
|
{
|
||||||
|
private static ButtonRolloverHandler? _remap_rollover;
|
||||||
|
private static SpriteRenderer? _remap_background;
|
||||||
|
private static bool _conflicts => KeybindManager.GetConflicts().Count > 0;
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(OptionsMenuBehaviour.Open))]
|
||||||
|
private static void OpenPostfix(OptionsMenuBehaviour __instance)
|
||||||
|
{
|
||||||
|
_remap_rollover = GameObject.Find("Remap Controls")?.GetComponent<ButtonRolloverHandler>()!;
|
||||||
|
_remap_background = _remap_rollover.transform.FindChild("Background").GetComponent<SpriteRenderer>();
|
||||||
|
_remap_background.color = _conflicts ? Color.red : Color.white;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(OptionsMenuBehaviour.Update))]
|
||||||
|
private static void UpdatePostfix(OptionsMenuBehaviour __instance)
|
||||||
|
{
|
||||||
|
if (_remap_rollover == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_remap_background == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_remap_rollover.OutColor = _conflicts ? Color.red : Color.white;
|
||||||
|
_remap_rollover.UnselectedColor = _conflicts ? Color.red : Color.white;
|
||||||
|
_remap_rollover.OverColor = _conflicts ? new Color32(255, 55, 55, 255) : Palette.AcceptedGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,232 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
using Reactor.Utilities.Extensions;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.Events;
|
||||||
|
using UnityEngine.UI;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.LocalSettings;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(OptionsMenuBehaviour))]
|
||||||
|
public static class OptionsMenuPatches
|
||||||
|
{
|
||||||
|
internal static OptionsMenuBehaviour? Instance { get; private set; }
|
||||||
|
internal static BoxCollider2D MaskCollider;
|
||||||
|
private static SpriteRenderer? background;
|
||||||
|
|
||||||
|
private static int currentPage = 1;
|
||||||
|
private static Dictionary<int, List<GameObject>> tabButtons = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates the tabs and their content
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(OptionsMenuBehaviour.Start))]
|
||||||
|
public static void StartPostfix(OptionsMenuBehaviour __instance)
|
||||||
|
{
|
||||||
|
Instance = __instance;
|
||||||
|
// Fix for tabs not being clickable in the main menu
|
||||||
|
if (!AmongUsClient.Instance.IsInGame)
|
||||||
|
{
|
||||||
|
__instance.Background.GetComponent<BoxCollider2D>().enabled = false;
|
||||||
|
__instance.transform.FindChild("Tint").SetLocalZ(5.6f);
|
||||||
|
}
|
||||||
|
|
||||||
|
background = __instance.Background;
|
||||||
|
|
||||||
|
var maskObj = new GameObject
|
||||||
|
{
|
||||||
|
layer = 5,
|
||||||
|
name = "SpriteMask",
|
||||||
|
};
|
||||||
|
maskObj.transform.SetParent(__instance.transform);
|
||||||
|
maskObj.transform.localPosition = new Vector3(0, -0.3f, 0);
|
||||||
|
maskObj.transform.localScale = new Vector3(500, 120, 1);
|
||||||
|
|
||||||
|
var blank = Sprite.Create(
|
||||||
|
Texture2D.whiteTexture,
|
||||||
|
new Rect(0, 0, Texture2D.whiteTexture.width, Texture2D.whiteTexture.height),
|
||||||
|
new Vector2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
var mask = maskObj.AddComponent<SpriteMask>();
|
||||||
|
mask.sprite = blank;
|
||||||
|
mask.isCustomRangeActive = true;
|
||||||
|
mask.sortingLayerName = "Default";
|
||||||
|
mask.backSortingOrder = 100;
|
||||||
|
mask.frontSortingOrder = 500;
|
||||||
|
|
||||||
|
MaskCollider = maskObj.AddComponent<BoxCollider2D>();
|
||||||
|
MaskCollider.size = new Vector2(0.01f, 0.1f);
|
||||||
|
MaskCollider.isTrigger = true;
|
||||||
|
MaskCollider.enabled = true;
|
||||||
|
|
||||||
|
currentPage = 1;
|
||||||
|
tabButtons.Clear();
|
||||||
|
float yOffset = 0;
|
||||||
|
int i = 0;
|
||||||
|
int tabIdx = 0;
|
||||||
|
int page = 1;
|
||||||
|
foreach (var settings in LocalSettingsManager.Tabs)
|
||||||
|
{
|
||||||
|
var tab = settings.CreateTab(__instance);
|
||||||
|
if (settings.ShouldCreateButton)
|
||||||
|
{
|
||||||
|
var button = settings.CreateTabButton(__instance, ref tabIdx, ref yOffset);
|
||||||
|
button.GetComponent<TabGroup>().Content = tab;
|
||||||
|
button.SetActive(page == currentPage);
|
||||||
|
|
||||||
|
if (!tabButtons.TryGetValue(page, out var list))
|
||||||
|
{
|
||||||
|
tabButtons.Add(page, new());
|
||||||
|
list = tabButtons[page];
|
||||||
|
}
|
||||||
|
list.Add(button);
|
||||||
|
yOffset += 0.6f;
|
||||||
|
tabIdx++;
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i > 7)
|
||||||
|
{
|
||||||
|
i = 0;
|
||||||
|
yOffset = 0;
|
||||||
|
page++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ChangePage(int increase)
|
||||||
|
{
|
||||||
|
currentPage += increase;
|
||||||
|
if (currentPage > tabButtons.Count)
|
||||||
|
{
|
||||||
|
currentPage = 1;
|
||||||
|
}
|
||||||
|
if (currentPage < 1)
|
||||||
|
{
|
||||||
|
currentPage = tabButtons.Count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create page buttons
|
||||||
|
// TODO: In-game menu has no close button. I tried making one from scratch but failed. Add one later plz
|
||||||
|
if (tabButtons.Count <= 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var nextButton = Object.Instantiate(__instance.BackButton, __instance.transform).GetComponent<PassiveButton>();
|
||||||
|
nextButton.transform.localPosition = new Vector3(3.2f, 2.6f, -10);
|
||||||
|
nextButton.transform.localScale = new Vector2(0.25f, 0.25f);
|
||||||
|
var nextRend = nextButton.GetComponent<SpriteRenderer>();
|
||||||
|
nextRend.sprite = MiraAssets.NextButton.LoadAsset();
|
||||||
|
nextButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
||||||
|
|
||||||
|
var nextRollover = nextButton.gameObject.AddComponent<ButtonRolloverHandler>();
|
||||||
|
nextRollover.Target = nextRend;
|
||||||
|
nextRollover.OverColor = Palette.AcceptedGreen;
|
||||||
|
|
||||||
|
nextButton.OnClick = new Button.ButtonClickedEvent();
|
||||||
|
nextButton.OnClick.AddListener(
|
||||||
|
(UnityAction)(() =>
|
||||||
|
{
|
||||||
|
ChangePage(1);
|
||||||
|
UpdatePages();
|
||||||
|
}));
|
||||||
|
|
||||||
|
var backButton = Object.Instantiate(__instance.BackButton, __instance.transform).GetComponent<PassiveButton>();
|
||||||
|
backButton.transform.localPosition = new Vector3(2.9f, 2.6f, -10);
|
||||||
|
backButton.transform.localScale = new Vector2(0.25f, 0.25f);
|
||||||
|
var backRend = backButton.GetComponent<SpriteRenderer>();
|
||||||
|
backRend.sprite = MiraAssets.NextButton.LoadAsset();
|
||||||
|
backRend.flipX = true;
|
||||||
|
backButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
||||||
|
|
||||||
|
var backRollover = backButton.gameObject.AddComponent<ButtonRolloverHandler>();
|
||||||
|
backRollover.Target = backRend;
|
||||||
|
backRollover.OverColor = Palette.AcceptedGreen;
|
||||||
|
|
||||||
|
backButton.OnClick = new Button.ButtonClickedEvent();
|
||||||
|
backButton.OnClick.AddListener(
|
||||||
|
(UnityAction)(() =>
|
||||||
|
{
|
||||||
|
ChangePage(-1);
|
||||||
|
UpdatePages();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Modifies how opening tabs is handled for the custom ones to work.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(OptionsMenuBehaviour.OpenTabGroup))]
|
||||||
|
public static bool OpenTabGroupPrefix(OptionsMenuBehaviour __instance, ref int index)
|
||||||
|
{
|
||||||
|
__instance.Tabs.ToList().ForEach(x => x.Close()); // Close all vanilla tabs
|
||||||
|
LocalSettingsManager.Tabs.ForEach(CustomClose); // Close all mod tabs
|
||||||
|
|
||||||
|
// Tabs with index 10 and above are the mod settings tabs
|
||||||
|
if (index >= 10)
|
||||||
|
{
|
||||||
|
CustomOpen(LocalSettingsManager.Tabs[index - 10]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes the custom tabs close when opening the options menu and resets pages.
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPostfix]
|
||||||
|
[HarmonyPatch(nameof(OptionsMenuBehaviour.Open))]
|
||||||
|
public static void OpenPostfix(OptionsMenuBehaviour __instance)
|
||||||
|
{
|
||||||
|
LocalSettingsManager.Tabs.ForEach(CustomClose);
|
||||||
|
currentPage = 1;
|
||||||
|
UpdatePages();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdatePages()
|
||||||
|
{
|
||||||
|
foreach (var pages in tabButtons)
|
||||||
|
{
|
||||||
|
pages.Value?
|
||||||
|
.Where(x => x != null)
|
||||||
|
.Do(
|
||||||
|
x => x?.SetActive(
|
||||||
|
pages.Key == currentPage));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CustomOpen(LocalSettingsTab tab)
|
||||||
|
{
|
||||||
|
if (tab.TabButton?.Button)
|
||||||
|
tab.TabButton!.Button.color = tab.TabAppearance.TabButtonHoverColor;
|
||||||
|
|
||||||
|
if (tab.TabButton?.Rollover)
|
||||||
|
tab.TabButton!.Rollover.OutColor = tab.TabAppearance.TabButtonHoverColor;
|
||||||
|
|
||||||
|
if (tab.TabButton?.Content)
|
||||||
|
tab.TabButton!.Content.SetActive(true);
|
||||||
|
|
||||||
|
if (background != null)
|
||||||
|
background.color = tab.TabAppearance.TabColor;
|
||||||
|
}
|
||||||
|
private static void CustomClose(LocalSettingsTab tab)
|
||||||
|
{
|
||||||
|
if (tab.TabButton?.Button)
|
||||||
|
tab.TabButton!.Button.color = tab.TabAppearance.TabButtonColor;
|
||||||
|
|
||||||
|
if (tab.TabButton?.Rollover)
|
||||||
|
tab.TabButton!.Rollover.OutColor = tab.TabAppearance.TabButtonColor;
|
||||||
|
|
||||||
|
if (tab.TabButton?.Content)
|
||||||
|
tab.TabButton!.Content.SetActive(false);
|
||||||
|
|
||||||
|
if (background != null)
|
||||||
|
background.color = Color.white;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using HarmonyLib;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MiraAPI.Patches.LocalSettings;
|
||||||
|
|
||||||
|
[HarmonyPatch(typeof(ToggleButtonBehaviour))]
|
||||||
|
public static class ToggleBehaviourPatch
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ResetText was inlined ofc.
|
||||||
|
/// Skill issue
|
||||||
|
/// </summary>
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(ToggleButtonBehaviour.UpdateText))]
|
||||||
|
public static bool UpdateTextPrefix(ToggleButtonBehaviour __instance, bool on)
|
||||||
|
{
|
||||||
|
var color = on ? new Color(0f, 1f, 0.16470589f, 1f) : Color.white;
|
||||||
|
__instance.onState = on;
|
||||||
|
__instance.Background.color = color;
|
||||||
|
|
||||||
|
// Used to mask the text
|
||||||
|
__instance.Text.richText = true;
|
||||||
|
__instance.Text.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">" +
|
||||||
|
$"{DestroyableSingleton<TranslationController>.Instance.GetString(__instance.BaseText)}: <b>" +
|
||||||
|
$"{DestroyableSingleton<TranslationController>.Instance.GetString(__instance.onState ? StringNames.SettingsOn : StringNames.SettingsOff)}</b></font>";
|
||||||
|
__instance.Text.sortingOrder = 151;
|
||||||
|
__instance.Text.renderer.sortingLayerName = "Default";
|
||||||
|
__instance.Text.renderer.sortingOrder = 151;
|
||||||
|
|
||||||
|
if (__instance.Rollover)
|
||||||
|
{
|
||||||
|
__instance.Rollover.ChangeOutColor(color);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ using HarmonyLib;
|
|||||||
using MiraAPI.PluginLoading;
|
using MiraAPI.PluginLoading;
|
||||||
using MiraAPI.Presets;
|
using MiraAPI.Presets;
|
||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
|
using MiraAPI.Utilities;
|
||||||
using MiraAPI.Utilities.Assets;
|
using MiraAPI.Utilities.Assets;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
using Reactor.Utilities.Extensions;
|
using Reactor.Utilities.Extensions;
|
||||||
@@ -28,22 +29,6 @@ internal static class GamePresetsTabPatches
|
|||||||
|
|
||||||
private static readonly object Lock = new();
|
private static readonly object Lock = new();
|
||||||
|
|
||||||
public static void DivideSize(GameObject obj, float amount)
|
|
||||||
{
|
|
||||||
foreach (var collider in obj.GetComponentsInChildren<Collider2D>(true))
|
|
||||||
{
|
|
||||||
if (collider.TryCast<BoxCollider2D>() is { } col)
|
|
||||||
{
|
|
||||||
col.size = new Vector2(col.size.x / amount, col.size.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var rend in obj.GetComponentsInChildren<SpriteRenderer>(true))
|
|
||||||
{
|
|
||||||
rend.size = new Vector2(rend.size.x / amount, rend.size.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnEnable))]
|
[HarmonyPatch(typeof(GamePresetsTab), nameof(GamePresetsTab.OnEnable))]
|
||||||
public static class GamePresetsOnEnablePatch
|
public static class GamePresetsOnEnablePatch
|
||||||
{
|
{
|
||||||
@@ -207,7 +192,7 @@ internal static class GamePresetsTabPatches
|
|||||||
saveText.transform.parent.localPosition.y,
|
saveText.transform.parent.localPosition.y,
|
||||||
saveText.transform.parent.localPosition.z);
|
saveText.transform.parent.localPosition.z);
|
||||||
|
|
||||||
DivideSize(saveButton.gameObject, 2f);
|
Helpers.DivideSize(saveButton.gameObject, 2f);
|
||||||
|
|
||||||
saveButton.OnClick = new Button.ButtonClickedEvent();
|
saveButton.OnClick = new Button.ButtonClickedEvent();
|
||||||
saveButton.OnClick.AddListener(
|
saveButton.OnClick.AddListener(
|
||||||
@@ -276,7 +261,7 @@ internal static class GamePresetsTabPatches
|
|||||||
var iconSpriteRend = icon.AddComponent<SpriteRenderer>();
|
var iconSpriteRend = icon.AddComponent<SpriteRenderer>();
|
||||||
iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset();
|
iconSpriteRend.sprite = MiraAssets.RefreshIcon.LoadAsset();
|
||||||
|
|
||||||
DivideSize(refreshButton.gameObject, 2f);
|
Helpers.DivideSize(refreshButton.gameObject, 2f);
|
||||||
refreshButton.buttonText.gameObject.Destroy();
|
refreshButton.buttonText.gameObject.Destroy();
|
||||||
|
|
||||||
refreshButton.OnClick = new Button.ButtonClickedEvent();
|
refreshButton.OnClick = new Button.ButtonClickedEvent();
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using HarmonyLib;
|
using HarmonyLib;
|
||||||
@@ -528,7 +528,7 @@ public static class RoleSettingMenuPatches
|
|||||||
roleOptionSetting.CountPlusBtn.SetInteractable(true);
|
roleOptionSetting.CountPlusBtn.SetInteractable(true);
|
||||||
__instance.roleChances.Add(roleOptionSetting);
|
__instance.roleChances.Add(roleOptionSetting);
|
||||||
|
|
||||||
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
|
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.25f, -0.2923f, 0f);
|
||||||
roleOptionSetting.titleText.color = customRole.OptionsMenuColor.FindAlternateColor();
|
roleOptionSetting.titleText.color = customRole.OptionsMenuColor.FindAlternateColor();
|
||||||
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
||||||
|
|
||||||
@@ -551,6 +551,18 @@ public static class RoleSettingMenuPatches
|
|||||||
passiveButton.interactableHoveredColor = Color.white;
|
passiveButton.interactableHoveredColor = Color.white;
|
||||||
|
|
||||||
passiveButton.OnClick.AddListener((UnityAction)(() => { ChangeTab(role, __instance); }));
|
passiveButton.OnClick.AddListener((UnityAction)(() => { ChangeTab(role, __instance); }));
|
||||||
|
|
||||||
|
if (customRole.Configuration.Icon != null)
|
||||||
|
{
|
||||||
|
var roleIcon = new GameObject("RoleIcon");
|
||||||
|
roleIcon.transform.parent = roleOptionSetting.transform;
|
||||||
|
roleIcon.transform.localScale = new(.25f, .25f, 1);
|
||||||
|
roleIcon.layer = LayerMask.NameToLayer("UI");
|
||||||
|
roleIcon.transform.localPosition = new Vector3(-1.3f, -0.3f, -2f);
|
||||||
|
var rend = roleIcon.AddComponent<SpriteRenderer>();
|
||||||
|
rend.sprite = customRole.Configuration.Icon.LoadAsset();
|
||||||
|
rend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customRole.Configuration is { MaxRoleCount: 0 })
|
if (customRole.Configuration is { MaxRoleCount: 0 })
|
||||||
|
|||||||
@@ -38,8 +38,13 @@ internal static class EjectionPatches
|
|||||||
{
|
{
|
||||||
public static void Postfix()
|
public static void Postfix()
|
||||||
{
|
{
|
||||||
var @event = new RoundStartEvent(false);
|
var @event = new BeforeRoundStartEvent(false);
|
||||||
MiraEventManager.InvokeEvent(@event);
|
MiraEventManager.InvokeEvent(@event);
|
||||||
|
|
||||||
|
if (@event.IsCancelled) return;
|
||||||
|
|
||||||
|
var @event2 = new RoundStartEvent(false);
|
||||||
|
MiraEventManager.InvokeEvent(@event2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,7 +12,7 @@ namespace MiraAPI.Patches.Roles;
|
|||||||
public static class HudManagerPatches
|
public static class HudManagerPatches
|
||||||
{
|
{
|
||||||
// Custom role tab.
|
// Custom role tab.
|
||||||
private static TaskPanelBehaviour? _roleTab;
|
public static TaskPanelBehaviour? RoleTab;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fixes Kill Button not showing for Neutral killing role.
|
/// Fixes Kill Button not showing for Neutral killing role.
|
||||||
@@ -38,9 +38,9 @@ public static class HudManagerPatches
|
|||||||
__instance.SabotageButton.gameObject.SetActive(isActive && customRole.Configuration.CanUseSabotage);
|
__instance.SabotageButton.gameObject.SetActive(isActive && customRole.Configuration.CanUseSabotage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_roleTab)
|
if (RoleTab)
|
||||||
{
|
{
|
||||||
_roleTab?.gameObject.SetActive(isActive);
|
RoleTab?.gameObject.SetActive(isActive);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,16 +62,16 @@ public static class HudManagerPatches
|
|||||||
|
|
||||||
if (role is ICustomRole { Configuration.RoleHintType: RoleHintType.RoleTab } customRole)
|
if (role is ICustomRole { Configuration.RoleHintType: RoleHintType.RoleTab } customRole)
|
||||||
{
|
{
|
||||||
if (_roleTab == null)
|
if (RoleTab == null)
|
||||||
{
|
{
|
||||||
_roleTab = CustomRoleManager.CreateRoleTab(customRole);
|
RoleTab = CustomRoleManager.CreateRoleTab(customRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomRoleManager.UpdateRoleTab(_roleTab, customRole);
|
CustomRoleManager.UpdateRoleTab(RoleTab, customRole);
|
||||||
}
|
}
|
||||||
else if (_roleTab)
|
else if (RoleTab)
|
||||||
{
|
{
|
||||||
_roleTab?.gameObject.Destroy();
|
RoleTab?.gameObject.Destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,16 +1,78 @@
|
|||||||
using HarmonyLib;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using BepInEx.Unity.IL2CPP.Utils.Collections;
|
||||||
|
using HarmonyLib;
|
||||||
|
using MiraAPI.Events;
|
||||||
|
using MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
|
using MiraAPI.Voting;
|
||||||
|
using Reactor.Utilities;
|
||||||
|
using Reactor.Utilities.Extensions;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace MiraAPI.Patches.Voting;
|
namespace MiraAPI.Patches.Voting;
|
||||||
|
|
||||||
[HarmonyPatch(typeof(DummyBehaviour), nameof(DummyBehaviour.Update))]
|
[HarmonyPatch(typeof(DummyBehaviour))]
|
||||||
internal static class DummyBehaviourPatch
|
internal static class DummyBehaviourPatches
|
||||||
{
|
{
|
||||||
public static void Postfix(DummyBehaviour __instance)
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(nameof(DummyBehaviour.Update))]
|
||||||
|
public static bool DummyUpdatePatch(DummyBehaviour __instance)
|
||||||
{
|
{
|
||||||
if (MeetingHud.Instance && __instance.myPlayer.GetVoteData())
|
NetworkedPlayerInfo data = __instance.myPlayer.Data;
|
||||||
|
if (data == null || data.IsDead) return false;
|
||||||
|
|
||||||
|
if (MeetingHud.Instance)
|
||||||
{
|
{
|
||||||
__instance.voted = __instance.myPlayer.GetVoteData().VotesRemaining == 0;
|
if (!__instance.voted)
|
||||||
|
{
|
||||||
|
__instance.StartCoroutine(CoDoVote(__instance).WrapToIl2Cpp());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
__instance.voted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerator CoDoVote(DummyBehaviour dummy)
|
||||||
|
{
|
||||||
|
dummy.voted = true;
|
||||||
|
yield return new WaitForSeconds(dummy.voteTime.Next());
|
||||||
|
|
||||||
|
var dummyVoteEvent = new DummyVoteEvent(dummy);
|
||||||
|
MiraEventManager.InvokeEvent(dummyVoteEvent);
|
||||||
|
|
||||||
|
if (dummyVoteEvent.IsCancelled)
|
||||||
|
{
|
||||||
|
dummy.voted = false;
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<byte> potentialSuspects = new();
|
||||||
|
potentialSuspects.AddRange(PlayerControl.AllPlayerControls
|
||||||
|
.ToArray()
|
||||||
|
.Where(p => p != dummy.myPlayer)
|
||||||
|
.Where(p => dummyVoteEvent.PlayerIsValid(p))
|
||||||
|
.Select(p => p.PlayerId));
|
||||||
|
|
||||||
|
if (dummyVoteEvent.CanSkip)
|
||||||
|
{
|
||||||
|
potentialSuspects.Add(253);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CanVote(dummy))
|
||||||
|
{
|
||||||
|
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, dummy.myPlayer.PlayerId, potentialSuspects.Random());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool CanVote(this DummyBehaviour dummy)
|
||||||
|
{
|
||||||
|
var dummyVoteData = dummy.myPlayer.GetVoteData();
|
||||||
|
return dummyVoteData.VotesRemaining > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -15,6 +15,24 @@ namespace MiraAPI.Patches.Voting;
|
|||||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony Convention")]
|
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony Convention")]
|
||||||
internal static class MeetingHudPatches
|
internal static class MeetingHudPatches
|
||||||
{
|
{
|
||||||
|
[HarmonyPrefix]
|
||||||
|
[HarmonyPatch(typeof(PlayerVoteArea), nameof(PlayerVoteArea.VoteForMe))]
|
||||||
|
public static bool VotePatch(PlayerVoteArea __instance)
|
||||||
|
{
|
||||||
|
var beforeVoteEvent = new BeforeVoteEvent(__instance, PlayerControl.LocalPlayer);
|
||||||
|
MiraEventManager.InvokeEvent(beforeVoteEvent);
|
||||||
|
|
||||||
|
if (beforeVoteEvent.IsCancelled)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var afterVoteEvent = new AfterVoteEvent(__instance, PlayerControl.LocalPlayer);
|
||||||
|
MiraEventManager.InvokeEvent(afterVoteEvent);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
[HarmonyPostfix]
|
[HarmonyPostfix]
|
||||||
[HarmonyPatch(nameof(MeetingHud.Start))]
|
[HarmonyPatch(nameof(MeetingHud.Start))]
|
||||||
public static void MeetingHudStartPatch(MeetingHud __instance)
|
public static void MeetingHudStartPatch(MeetingHud __instance)
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using BepInEx.Configuration;
|
|||||||
using MiraAPI.GameModes;
|
using MiraAPI.GameModes;
|
||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Presets;
|
using MiraAPI.Presets;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using BepInEx.Configuration;
|
||||||
using BepInEx.Unity.IL2CPP;
|
using BepInEx.Unity.IL2CPP;
|
||||||
using MiraAPI.Colors;
|
using MiraAPI.Colors;
|
||||||
using MiraAPI.Events;
|
using MiraAPI.Events;
|
||||||
@@ -11,12 +12,16 @@ using MiraAPI.GameEnd;
|
|||||||
using MiraAPI.GameOptions;
|
using MiraAPI.GameOptions;
|
||||||
using MiraAPI.GameOptions.Attributes;
|
using MiraAPI.GameOptions.Attributes;
|
||||||
using MiraAPI.Hud;
|
using MiraAPI.Hud;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using MiraAPI.LocalSettings;
|
||||||
|
using MiraAPI.LocalSettings.Attributes;
|
||||||
using MiraAPI.Modifiers;
|
using MiraAPI.Modifiers;
|
||||||
using MiraAPI.Presets;
|
using MiraAPI.Presets;
|
||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
using MiraAPI.Utilities;
|
using MiraAPI.Utilities;
|
||||||
using Reactor.Networking;
|
using Reactor.Networking;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
|
using Rewired;
|
||||||
|
|
||||||
namespace MiraAPI.PluginLoading;
|
namespace MiraAPI.PluginLoading;
|
||||||
|
|
||||||
@@ -90,6 +95,11 @@ public sealed class MiraPluginManager
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (RegisterLocalTabs(type, plugin))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (RegisterRole(type, info, out var role))
|
if (RegisterRole(type, info, out var role))
|
||||||
{
|
{
|
||||||
roles.Add(role);
|
roles.Add(role);
|
||||||
@@ -107,6 +117,7 @@ public sealed class MiraPluginManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
RegisterColorClasses(type);
|
RegisterColorClasses(type);
|
||||||
|
RegisterKeybinds(type, plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
info.PluginConfig.Save();
|
info.PluginConfig.Save();
|
||||||
@@ -129,10 +140,13 @@ public sealed class MiraPluginManager
|
|||||||
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
||||||
|
|
||||||
// Cache all the registered plugins into an array for easy access
|
// Cache all the registered plugins into an array for easy access
|
||||||
RegisteredPlugins = [.._registeredPlugins.Values];
|
RegisteredPlugins = [.. _registeredPlugins.Values];
|
||||||
|
|
||||||
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
|
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
RegisterKeybinds(typeof(MiraGlobalKeybinds), PluginSingleton<MiraApiPlugin>.Instance);
|
||||||
|
RegisterLocalTabs(typeof(MiraApiSettings), PluginSingleton<MiraApiPlugin>.Instance);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -303,4 +317,125 @@ public sealed class MiraPluginManager
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool RegisterLocalTabs(Type type, BasePlugin pluginInfo)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (!type.IsAssignableTo(typeof(LocalSettingsTab)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!LocalSettingsManager.RegisterTab(type, pluginInfo))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in type.GetProperties())
|
||||||
|
{
|
||||||
|
if (LocalSettingsManager.TypeToTab[type] is not { } tabInstance)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.GetCustomAttribute<LocalSettingsButtonAttribute>() != null &&
|
||||||
|
property.PropertyType.IsAssignableTo(typeof(LocalSettingsButton)))
|
||||||
|
{
|
||||||
|
var button = property.GetValue(tabInstance) as LocalSettingsButton;
|
||||||
|
button!.Tab = tabInstance;
|
||||||
|
tabInstance.Buttons.Add(button);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!typeof(ConfigEntryBase).IsAssignableFrom(property.PropertyType))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.GetMethod?.IsStatic == true)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"Option property {property.Name} in {type.Name} must not be static.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.GetValue(tabInstance) is not ConfigEntryBase configEntry)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"Option property {property.Name} in {type.Name} has to be a config entry.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var attribute = property.GetCustomAttribute<LocalSettingAttribute>();
|
||||||
|
if (attribute == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
attribute.CreateSetting(type, configEntry);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(ConfigEntryBase)) && f.GetCustomAttribute<LocalSettingAttribute>() != null))
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for local settings.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterKeybinds(Type type, BasePlugin source)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (type.GetCustomAttribute<RegisterCustomKeybindsAttribute>() == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!type.IsStatic())
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"Keybinds class {type.Name} must be static.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var property in type.GetProperties())
|
||||||
|
{
|
||||||
|
if (property.PropertyType != typeof(MiraKeybind))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (property.GetValue(null) is not MiraKeybind keybind)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"Keybind property {property.Name} in {type.Name} is not a MiraKeybind.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
KeybindManager.Keybinds.Add(keybind);
|
||||||
|
if (source is IMiraPlugin miraPlugin)
|
||||||
|
{
|
||||||
|
keybind.SourcePluginName = miraPlugin.OptionsTitleText;
|
||||||
|
}
|
||||||
|
else if (source is MiraApiPlugin)
|
||||||
|
{
|
||||||
|
keybind.SourcePluginName = "MiraAPI";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(MiraKeybind))))
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for keybinds.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger<MiraApiPlugin>.Error($"Failed to register keybind class {type.Name}: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 5.1 KiB |
@@ -70,6 +70,11 @@ public static class MiraAssets
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LoadableResourceAsset NextButton { get; } = new("MiraAPI.Resources.NextButton.png");
|
public static LoadableResourceAsset NextButton { get; } = new("MiraAPI.Resources.NextButton.png");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the Mira settings icon..
|
||||||
|
/// </summary>
|
||||||
|
public static LoadableResourceAsset SettingsIcon { get; } = new("MiraAPI.Resources.Settings.png");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the highlighted Next Button sprite.
|
/// Gets the highlighted Next Button sprite.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -94,4 +99,14 @@ public static class MiraAssets
|
|||||||
/// Gets the white CategoryHeader used in the Settings Menu.
|
/// Gets the white CategoryHeader used in the Settings Menu.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LoadableResourceAsset CategoryHeader { get; } = new("MiraAPI.Resources.CategoryHeader.png");
|
public static LoadableResourceAsset CategoryHeader { get; } = new("MiraAPI.Resources.CategoryHeader.png");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the sprite used for the Keybind icons.
|
||||||
|
/// </summary>
|
||||||
|
public static LoadableResourceAsset KeybindButton { get; } = new("MiraAPI.Resources.KeybindButton.png");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the sprite used for timed modifier file in TaskAdderGame.
|
||||||
|
/// </summary>
|
||||||
|
public static LoadableResourceAsset TimedModifierFile { get; } = new("MiraAPI.Resources.TimedModifierFile.png");
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,8 @@ using MiraAPI.Networking;
|
|||||||
using MiraAPI.Roles;
|
using MiraAPI.Roles;
|
||||||
using MiraAPI.Voting;
|
using MiraAPI.Voting;
|
||||||
using Reactor.Utilities;
|
using Reactor.Utilities;
|
||||||
|
using Rewired;
|
||||||
|
using Rewired.Data;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
|
||||||
@@ -92,7 +94,7 @@ public static class Extensions
|
|||||||
/// <param name="timer">The current timer value.</param>
|
/// <param name="timer">The current timer value.</param>
|
||||||
/// <param name="maxTimer">The maximum timer value.</param>
|
/// <param name="maxTimer">The maximum timer value.</param>
|
||||||
/// <param name="format">The format string to use for the timer text.</param>
|
/// <param name="format">The format string to use for the timer text.</param>
|
||||||
public static void SetCooldownFormat(this ActionButton button, float timer, float maxTimer, string format="0")
|
public static void SetCooldownFormat(this ActionButton? button, float timer, float maxTimer, string format = "0")
|
||||||
{
|
{
|
||||||
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
||||||
button.isCoolingDown = num > 0f;
|
button.isCoolingDown = num > 0f;
|
||||||
@@ -113,7 +115,7 @@ public static class Extensions
|
|||||||
/// <param name="timer">The current timer value.</param>
|
/// <param name="timer">The current timer value.</param>
|
||||||
/// <param name="maxTimer">The maximum timer value.</param>
|
/// <param name="maxTimer">The maximum timer value.</param>
|
||||||
/// <param name="format">The format string to use for the timer text.</param>
|
/// <param name="format">The format string to use for the timer text.</param>
|
||||||
public static void SetFillUpFormat(this ActionButton button, float timer, float maxTimer, string format="0")
|
public static void SetFillUpFormat(this ActionButton button, float timer, float maxTimer, string format = "0")
|
||||||
{
|
{
|
||||||
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
||||||
button.isCoolingDown = num > 0f;
|
button.isCoolingDown = num > 0f;
|
||||||
@@ -513,6 +515,7 @@ public static class Extensions
|
|||||||
/// <param name="includeImpostors">Whether impostors should be included in the search.</param>
|
/// <param name="includeImpostors">Whether impostors should be included in the search.</param>
|
||||||
/// <param name="distance">The radius to search within.</param>
|
/// <param name="distance">The radius to search within.</param>
|
||||||
/// <param name="ignoreColliders">Whether colliders should be ignored when searching.</param>
|
/// <param name="ignoreColliders">Whether colliders should be ignored when searching.</param>
|
||||||
|
/// <param name="includeGhosts">Determines if Ghosts are included.</param>
|
||||||
/// <param name="predicate">Optional predicate to test if the object is valid.</param>
|
/// <param name="predicate">Optional predicate to test if the object is valid.</param>
|
||||||
/// <returns>The closest player if there is one, false otherwise.</returns>
|
/// <returns>The closest player if there is one, false otherwise.</returns>
|
||||||
public static PlayerControl? GetClosestPlayer(
|
public static PlayerControl? GetClosestPlayer(
|
||||||
@@ -520,16 +523,16 @@ public static class Extensions
|
|||||||
bool includeImpostors,
|
bool includeImpostors,
|
||||||
float distance,
|
float distance,
|
||||||
bool ignoreColliders = false,
|
bool ignoreColliders = false,
|
||||||
|
bool includeGhosts = false,
|
||||||
Predicate<PlayerControl>? predicate = null)
|
Predicate<PlayerControl>? predicate = null)
|
||||||
{
|
{
|
||||||
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders)
|
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders)
|
||||||
.Where(
|
.Where(
|
||||||
playerInfo => !playerInfo.Data.Disconnected &&
|
playerInfo => !playerInfo.Data.Disconnected &&
|
||||||
playerInfo.PlayerId != playerControl.PlayerId &&
|
playerInfo.PlayerId != playerControl.PlayerId &&
|
||||||
!playerInfo.Data.IsDead &&
|
(includeGhosts || !playerInfo.Data.IsDead) &&
|
||||||
(includeImpostors || !playerInfo.Data.Role.IsImpostor))
|
(includeImpostors || !playerInfo.Data.Role.IsImpostor))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault();
|
return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -544,4 +547,53 @@ public static class Extensions
|
|||||||
renderer.material.SetColor(ShaderID.OutlineColor, color ?? Color.clear);
|
renderer.material.SetColor(ShaderID.OutlineColor, color ?? Color.clear);
|
||||||
renderer.material.SetColor(ShaderID.AddColor, color ?? Color.clear);
|
renderer.material.SetColor(ShaderID.AddColor, color ?? Color.clear);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inspired by: https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/Keybinds.cs#L29
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Registers a new mod keybind as a user-assignable button action in Rewired.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="userData">The Rewired user data to add the action to.</param>
|
||||||
|
/// <param name="id">The internal name of the action.</param>
|
||||||
|
/// <param name="name">Text shown in the rebinding UI.</param>
|
||||||
|
/// /// <param name="group">Group shown above the label.</param>
|
||||||
|
/// <param name="key">The default key to assign to this action.</param>
|
||||||
|
/// <param name="category">Category ID to group actions in Rewired (default is 0).</param>
|
||||||
|
/// <param name="elementIdentifierId">The element identifier ID (default is -1, meaning none specified).</param>
|
||||||
|
/// <param name="type">The <see cref="InputActionType"/> for this action (default is Button).</param>
|
||||||
|
/// <param name="modifiers">Optional modifier keys (e.g., <c>Control</c>, <c>Shift</c>, <c>Alt</c>) that must be held together with the main key.</param>
|
||||||
|
/// <returns>The action ID of the newly registered action.</returns>
|
||||||
|
public static InputAction RegisterModBind(this UserData userData, string id, string name, string? group, KeyboardKeyCode key, int category = 0, int elementIdentifierId = -1, InputActionType type = InputActionType.Button, ModifierKey[]? modifiers = null)
|
||||||
|
{
|
||||||
|
userData.AddAction(category);
|
||||||
|
var action = userData.GetAction(userData.actions.Count - 1)!;
|
||||||
|
|
||||||
|
action.name = id;
|
||||||
|
action.descriptiveName = group != null
|
||||||
|
? $"<b><size=70%>{Palette.CrewmateRoleHeaderDarkBlue.ToTextColor()}{group.ReplaceLineEndings(" ")}</color></size></b>\n{name}"
|
||||||
|
: name;
|
||||||
|
action.categoryId = category;
|
||||||
|
action.type = type;
|
||||||
|
action.userAssignable = true;
|
||||||
|
|
||||||
|
var map = new ActionElementMap
|
||||||
|
{
|
||||||
|
_elementIdentifierId = elementIdentifierId,
|
||||||
|
_actionId = action.id,
|
||||||
|
_elementType = ControllerElementType.Button,
|
||||||
|
_axisContribution = Pole.Positive,
|
||||||
|
_keyboardKeyCode = key,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (modifiers != null)
|
||||||
|
{
|
||||||
|
if (modifiers.Length > 0) map._modifierKey1 = modifiers[0];
|
||||||
|
if (modifiers.Length > 1) map._modifierKey2 = modifiers[1];
|
||||||
|
if (modifiers.Length > 2) map._modifierKey3 = modifiers[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
userData.keyboardMaps[0].actionElementMaps.Add(map);
|
||||||
|
userData.joystickMaps[0].actionElementMaps.Add(map);
|
||||||
|
return action;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
using System.Collections;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||||
|
using MiraAPI.Keybinds;
|
||||||
|
using MiraAPI.Utilities.Assets;
|
||||||
|
using Rewired;
|
||||||
using TMPro;
|
using TMPro;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
@@ -19,7 +24,77 @@ public static class Helpers
|
|||||||
/// <returns>A list of alive players.</returns>
|
/// <returns>A list of alive players.</returns>
|
||||||
public static List<PlayerControl> GetAlivePlayers()
|
public static List<PlayerControl> GetAlivePlayers()
|
||||||
{
|
{
|
||||||
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x=>x.Object)];
|
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x => x.Object)];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static GameObject CreateKeybindIcon(GameObject button, KeyboardKeyCode keyCode, Vector3 localPos)
|
||||||
|
{
|
||||||
|
var keybindIcon = Object.Instantiate(HudManager.Instance.AbilityButton.usesRemainingSprite.gameObject, button.transform);
|
||||||
|
keybindIcon.GetComponent<SpriteRenderer>().sprite = MiraAssets.KeybindButton.LoadAsset();
|
||||||
|
keybindIcon.transform.GetComponentInChildren<TextMeshPro>().text = keyCode.ToString();
|
||||||
|
keybindIcon.name = "KeybindIcon";
|
||||||
|
keybindIcon.transform.localPosition = localPos;
|
||||||
|
return keybindIcon;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a draggable scroller. Add items into the scroller.Inner transform. This does not automatically position children.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="parent">Where the scroller should be parented to.</param>
|
||||||
|
/// <param name="hitBoxCollider">The collider of the scroller. Used to drag.</param>
|
||||||
|
/// <returns>The created scroller object.</returns>
|
||||||
|
public static Scroller CreateScroller(Transform parent, BoxCollider2D hitBoxCollider)
|
||||||
|
{
|
||||||
|
var scrollObj = new GameObject("Scroller");
|
||||||
|
scrollObj.transform.SetParent(parent);
|
||||||
|
scrollObj.transform.localScale = new Vector3(1, 1, 1);
|
||||||
|
|
||||||
|
var inner = new GameObject("Inner");
|
||||||
|
inner.transform.SetParent(scrollObj.transform);
|
||||||
|
inner.transform.localScale = new Vector3(1, 1, 1);
|
||||||
|
inner.transform.localPosition = new Vector3(0, 0, 2);
|
||||||
|
|
||||||
|
var scroller = scrollObj.AddComponent<Scroller>();
|
||||||
|
scroller.allowX = false;
|
||||||
|
scroller.allowY = true;
|
||||||
|
scroller.DragScrollSpeed = 1f;
|
||||||
|
scroller.Colliders = new Il2CppReferenceArray<Collider2D>([hitBoxCollider]);
|
||||||
|
scroller.Inner = inner.transform;
|
||||||
|
|
||||||
|
return scroller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Divides a button/etc by a certain amount by resizing colliders and renderer sizes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="obj">The object you want to divide.</param>
|
||||||
|
/// <param name="amount">How much you want to divide by.</param>
|
||||||
|
public static void DivideSize(GameObject obj, float amount)
|
||||||
|
{
|
||||||
|
foreach (var collider in obj.GetComponentsInChildren<Collider2D>(true))
|
||||||
|
{
|
||||||
|
if (collider.TryCast<BoxCollider2D>() is { } col)
|
||||||
|
{
|
||||||
|
col.size = new Vector2(col.size.x / amount, col.size.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var rend in obj.GetComponentsInChildren<SpriteRenderer>(true))
|
||||||
|
{
|
||||||
|
rend.size = new Vector2(rend.size.x / amount, rend.size.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the keybind for an action with ReInput.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="actionId">The action ID.</param>
|
||||||
|
/// <returns>The keyboard key code.</returns>
|
||||||
|
public static KeyboardKeyCode GetKeybindByActionId(int actionId)
|
||||||
|
{
|
||||||
|
var player = ReInput.players.GetPlayer(0);
|
||||||
|
return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false)
|
||||||
|
.keyboardKeyCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -36,10 +111,10 @@ public static class Helpers
|
|||||||
case 100:
|
case 100:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
var num = Random.RandomRangeInt(1, 101);
|
var num = UnityEngine.Random.RandomRangeInt(1, 101);
|
||||||
return num <= probability;
|
return num <= probability;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,6 +473,26 @@ public static class Helpers
|
|||||||
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
{
|
{
|
||||||
return new string(Enumerable.Repeat(chars, length)
|
return new string(Enumerable.Repeat(chars, length)
|
||||||
.Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray());
|
.Select(s => s[UnityEngine.Random.RandomRangeInt(0, s.Length)]).ToArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the formated value using the specified suffix and format string.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value">The value to format.</param>
|
||||||
|
/// <param name="suffix">The suffix to add.</param>
|
||||||
|
/// <param name="formatString">The format string to use to format.</param>
|
||||||
|
/// <returns>The formated value.</returns>
|
||||||
|
public static string FormatValue(float value, MiraNumberSuffixes suffix = MiraNumberSuffixes.None, string formatString = "0.0")
|
||||||
|
{
|
||||||
|
return suffix switch
|
||||||
|
{
|
||||||
|
MiraNumberSuffixes.None => value.ToString(formatString, NumberFormatInfo.InvariantInfo),
|
||||||
|
MiraNumberSuffixes.Multiplier => value.ToString(formatString, NumberFormatInfo.InvariantInfo) + "x",
|
||||||
|
MiraNumberSuffixes.Percent => value.ToString(formatString, NumberFormatInfo.InvariantInfo) + "%",
|
||||||
|
_ => TranslationController.Instance.GetString(
|
||||||
|
StringNames.GameSecondsAbbrev,
|
||||||
|
(Il2CppSystem.Object[])[value.ToString(formatString, CultureInfo.InvariantCulture)]),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,7 +71,7 @@ public static class VotingUtils
|
|||||||
/// <param name="source">The player who is sending the RPC. Should be the host.</param>
|
/// <param name="source">The player who is sending the RPC. Should be the host.</param>
|
||||||
/// <param name="voterId">The player who voted.</param>
|
/// <param name="voterId">The player who voted.</param>
|
||||||
/// <param name="votedFor">The player who the voter voted for.</param>
|
/// <param name="votedFor">The player who the voter voted for.</param>
|
||||||
[MethodRpc((uint)MiraRpc.RemoveVote, SendImmediately = true)]
|
[MethodRpc((uint)MiraRpc.RemoveVote)]
|
||||||
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
|
public static void RpcRemoveVote(PlayerControl source, byte voterId, byte votedFor)
|
||||||
{
|
{
|
||||||
if (!source.IsHost()) return;
|
if (!source.IsHost()) return;
|
||||||
@@ -107,7 +107,7 @@ public static class VotingUtils
|
|||||||
/// <param name="source">The player who sent this RPC.</param>
|
/// <param name="source">The player who sent this RPC.</param>
|
||||||
/// <param name="srcPlayerId">The id of the player who casted the vote.</param>
|
/// <param name="srcPlayerId">The id of the player who casted the vote.</param>
|
||||||
/// <param name="suspectPlayerId">The voted player's id.</param>
|
/// <param name="suspectPlayerId">The voted player's id.</param>
|
||||||
[MethodRpc((uint)MiraRpc.CastVote, SendImmediately = true)]
|
[MethodRpc((uint)MiraRpc.CastVote)]
|
||||||
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
|
public static void RpcCastVote(PlayerControl source, byte srcPlayerId, byte suspectPlayerId)
|
||||||
{
|
{
|
||||||
CustomCastVote(srcPlayerId, suspectPlayerId);
|
CustomCastVote(srcPlayerId, suspectPlayerId);
|
||||||
|
|||||||
Reference in new issue
Block a user