mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 11:39:45 +00:00
Compare commits
157
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fd01ffb1a | ||
|
|
f0f6ef1a17 | ||
|
|
0d1d1891a8 | ||
|
|
531a799a24 | ||
|
|
3e6eb49d93 | ||
|
|
a3fc847d07 | ||
|
|
ddfad0752d | ||
|
|
f1846375ca | ||
|
|
fcb693e899 | ||
|
|
21bec074fa | ||
|
|
31ffe6274c | ||
|
|
aef4d6daf1 | ||
|
|
bd0aa68cb8 | ||
|
|
f43a542134 | ||
|
|
efd176f7a4 | ||
|
|
b2fabdb139 | ||
|
|
3a36acd131 | ||
|
|
8c763e2a57 | ||
|
|
ce330325e4 | ||
|
|
89cd32d4a4 | ||
|
|
f0e37daf0e | ||
|
|
a5ddec48c8 | ||
|
|
5f4461f7e5 | ||
|
|
5e2ca191ba | ||
|
|
a0d3afc7c7 | ||
|
|
3cd7acbc70 | ||
|
|
31132e6e65 | ||
|
|
1a53e2b045 | ||
|
|
4fad586aa7 | ||
|
|
6d51e6806a | ||
|
|
b28a0a1253 | ||
|
|
51b54d4340 | ||
|
|
ea9bf162ea | ||
|
|
57105f487b | ||
|
|
c5e424e8ff | ||
|
|
521e5a12fd | ||
|
|
e6e4a86546 | ||
|
|
be46ee7329 | ||
|
|
75a15dc090 | ||
|
|
cffb104392 | ||
|
|
bb0b678747 | ||
|
|
f48143610c | ||
|
|
2cfc5ac039 | ||
|
|
e9beb3b423 | ||
|
|
7917d4b5d9 | ||
|
|
bf795a2e36 | ||
|
|
144a95fa3b | ||
|
|
e25b78705b | ||
|
|
19ba9da2c3 | ||
|
|
ce9864ae8b | ||
|
|
8f76f21cb1 | ||
|
|
bc7b835dc8 | ||
|
|
f0b2f9ee2a | ||
|
|
25eafe664b | ||
|
|
db327604c5 | ||
|
|
1462061df9 | ||
|
|
fb40664bd0 | ||
|
|
3cba870980 | ||
|
|
ab2f03ee4d | ||
|
|
645d1dad1c | ||
|
|
a04e0d28db | ||
|
|
b04d9619b8 | ||
|
|
33fd657eb3 | ||
|
|
529fc89fc5 | ||
|
|
6ab283d39c | ||
|
|
08d8a48701 | ||
|
|
708534ed5c | ||
|
|
886ef0e31f | ||
|
|
0b9e54eb50 | ||
|
|
bad6648f44 | ||
|
|
86f970c989 | ||
|
|
7aafaf478c | ||
|
|
451a626370 | ||
|
|
e02c06ca07 | ||
|
|
e43cd0ceff | ||
|
|
e680b5c872 | ||
|
|
b6f304fe1d | ||
|
|
e52f48c20d | ||
|
|
d97588b356 | ||
|
|
68304a5eb8 | ||
|
|
f2add2a65b | ||
|
|
bbff2dd2d9 | ||
|
|
664f8cb2e8 | ||
|
|
d9081ed17b | ||
|
|
6f248221d6 | ||
|
|
989b511276 | ||
|
|
b67fdf5ab1 | ||
|
|
26afd47d2a | ||
|
|
2e0ecd95a6 | ||
|
|
669d5765e6 | ||
|
|
309094bdf8 | ||
|
|
49d09bb44f | ||
|
|
f77b694e78 | ||
|
|
e3cf884975 | ||
|
|
3d91c8de25 | ||
|
|
3387564f2d | ||
|
|
9e2787b781 | ||
|
|
90ed9b2a98 | ||
|
|
530cb79981 | ||
|
|
6f4eff7b38 | ||
|
|
2159b6aa89 | ||
|
|
9ec270bd68 | ||
|
|
c9ff839f12 | ||
|
|
d1116b3fce | ||
|
|
65a81d0fb4 | ||
|
|
26b94b0127 | ||
|
|
6443b5932b | ||
|
|
a619a975d7 | ||
|
|
81834a7f1a | ||
|
|
3015c6dd6a | ||
|
|
0e2eeea069 | ||
|
|
7e4a71739c | ||
|
|
caa27642db | ||
|
|
4a8fb38380 | ||
|
|
66375e884f | ||
|
|
9ae9094a85 | ||
|
|
1c6fa9a84d | ||
|
|
0ebec6d0f1 | ||
|
|
0bed367eda | ||
|
|
176a1d5e5c | ||
|
|
fb19b4bc04 | ||
|
|
8fb8d18be9 | ||
|
|
b1846fae26 | ||
|
|
8b8f9ea5db | ||
|
|
bbec3c1958 | ||
|
|
758b1d3183 | ||
|
|
d8131d1f5d | ||
|
|
9ccf7104cb | ||
|
|
bc12c5a5f7 | ||
|
|
5dfbd5dc8f | ||
|
|
84c8f743f8 | ||
|
|
3905a3b394 | ||
|
|
fb5ff4b53e | ||
|
|
047312a197 | ||
|
|
8e0a8f0585 | ||
|
|
4849b52b47 | ||
|
|
9cc9a137cc | ||
|
|
055b097c21 | ||
|
|
87187fe205 | ||
|
|
975e8cb9b7 | ||
|
|
6775294a19 | ||
|
|
2af89a678f | ||
|
|
dda46e5903 | ||
|
|
ba92d9608c | ||
|
|
52ff4bfbfc | ||
|
|
b6dda5f970 | ||
|
|
9504862bea | ||
|
|
4b6ffc65df | ||
|
|
ccde63068b | ||
|
|
5e16429acf | ||
|
|
1e47500686 | ||
|
|
0b1cf998f8 | ||
|
|
14a3669031 | ||
|
|
ae79cd87d9 | ||
|
|
8e2535ada2 | ||
|
|
ce81d8c1ff | ||
|
|
9485099ab9 |
No files matched your search
+2
-2
@@ -2,10 +2,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Reactor" Version="2.3.1" />
|
||||
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
|
||||
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.10.29" PrivateAssets="all" />
|
||||
<PackageReference Include="AmongUs.GameLibs.Android" Version="2025.3.28" PrivateAssets="all" />
|
||||
|
||||
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" PrivateAssets="all" ExcludeAssets="runtime" />
|
||||
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.1.9</VersionPrefix>
|
||||
<VersionPrefix>0.2.0</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
|
||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||
@@ -34,7 +34,7 @@
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556" PrivateAssets="all" />
|
||||
<AdditionalFiles Include="$(MSBuildThisFileDirectory)\stylecop.json" Link="stylecop.json" Visible="false" />
|
||||
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="9.32.0.97167">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.7.0.110445">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
||||
@@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using MiraAPI.Example.Modifiers;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
|
||||
public class NeutralKillerButton : CustomActionButton
|
||||
{
|
||||
public override string Name => "Win Game";
|
||||
public override float Cooldown => 0f;
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
protected override void OnClick()
|
||||
{
|
||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||
}
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
return role is NeutralKillerRole;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using MiraAPI.Example.Options.Roles;
|
||||
using System.Collections;
|
||||
using MiraAPI.Example.Options.Roles;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons.Teleporter;
|
||||
@@ -19,7 +19,7 @@ public class TeleportButton : CustomActionButton
|
||||
public override int MaxUses => 2;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
||||
|
||||
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
||||
public static bool IsZoom { get; private set; }
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
using MiraAPI.Events;
|
||||
using System.Linq;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Events.Vanilla.Map;
|
||||
using MiraAPI.Events.Vanilla.Meeting;
|
||||
using MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.Example.Buttons.Freezer;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
@@ -12,11 +17,7 @@ public static class ExampleEventHandlers
|
||||
{
|
||||
public static void Initialize()
|
||||
{
|
||||
// Register event handlers here
|
||||
MiraEventManager.RegisterEventHandler<MiraButtonClickEvent<FreezeButton>>(FreezeButtonClickHandler, 1);
|
||||
MiraEventManager.RegisterEventHandler<MiraButtonCancelledEvent<FreezeButton>>(FreezeButtonCancelledHandler);
|
||||
MiraEventManager.RegisterEventHandler<UpdateSystemEvent>(UpdateSystemEventHandler);
|
||||
|
||||
// You can register event handlers with the MiraEventManager class.
|
||||
MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
|
||||
@@ -33,24 +34,59 @@ public static class ExampleEventHandlers
|
||||
});
|
||||
}
|
||||
|
||||
// If you want to add extra votes to a player, do something like this.
|
||||
[RegisterEvent]
|
||||
public static void StartMeetingEvent(StartMeetingEvent _)
|
||||
{
|
||||
foreach (var plr in PlayerControl.AllPlayerControls.ToArray().Where(player => player.Data.Role is MayorRole))
|
||||
{
|
||||
plr.GetVoteData().IncreaseRemainingVotes(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Example of using the Voting API to make a role similar to the Prosecutor from Town of Us.
|
||||
[RegisterEvent(15)]
|
||||
public static void HandleVoteEvent(HandleVoteEvent @event)
|
||||
{
|
||||
if (@event.VoteData.Owner.Data.Role is not NeutralKillerRole) return;
|
||||
|
||||
@event.VoteData.SetRemainingVotes(0);
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
@event.VoteData.VoteForPlayer(@event.TargetId);
|
||||
}
|
||||
|
||||
foreach (var plr in PlayerControl.AllPlayerControls.ToArray().Where(player => player != @event.VoteData.Owner))
|
||||
{
|
||||
plr.GetVoteData().Votes.Clear();
|
||||
plr.GetVoteData().VotesRemaining = 0;
|
||||
}
|
||||
|
||||
@event.Cancel();
|
||||
}
|
||||
|
||||
// Events can be registered using an attribute as well.
|
||||
[RegisterEvent]
|
||||
public static void UpdateSystemEventHandler(UpdateSystemEvent @event)
|
||||
{
|
||||
Logger<ExamplePlugin>.Error(@event.SystemType.ToString());
|
||||
}
|
||||
|
||||
// Example event handler
|
||||
[RegisterEvent(1)]
|
||||
public static void FreezeButtonClickHandler(MiraButtonClickEvent<FreezeButton> @event)
|
||||
{
|
||||
Logger<ExamplePlugin>.Warning("Freeze button clicked!");
|
||||
|
||||
if (PlayerControl.LocalPlayer.Data.PlayerName == "stupid")
|
||||
{
|
||||
@event.Cancel();
|
||||
@event.Button.SetTimer(15f);
|
||||
}
|
||||
if (PlayerControl.LocalPlayer.Data.PlayerName != "stupid") return;
|
||||
|
||||
@event.Cancel();
|
||||
@event.Button.SetTimer(15f);
|
||||
}
|
||||
|
||||
// Example event handler
|
||||
[RegisterEvent]
|
||||
public static void FreezeButtonCancelledHandler(MiraButtonCancelledEvent<FreezeButton> @event)
|
||||
{
|
||||
Logger<ExamplePlugin>.Warning("Freeze button cancelled!");
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.GameOver;
|
||||
|
||||
public class NeutralKillerGameOver : CustomGameOver
|
||||
{
|
||||
public override bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners)
|
||||
{
|
||||
return winners is [{ Role: NeutralKillerRole }];
|
||||
}
|
||||
|
||||
public override void AfterEndGameSetup(EndGameManager endGameManager)
|
||||
{
|
||||
endGameManager.WinText.text = "Outcast Killer Wins!";
|
||||
endGameManager.WinText.color = Color.magenta;
|
||||
endGameManager.BackgroundBar.material.SetColor(ShaderID.Color, Color.magenta);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Example.Options.Modifiers;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
@@ -7,13 +8,13 @@ public class CaptainModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "Captain";
|
||||
|
||||
public override int GetAmountPerGame()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override int GetAssignmentChance()
|
||||
{
|
||||
return 100;
|
||||
return (int)OptionGroupSingleton<CaptainModifierSettings>.Instance.Chance;
|
||||
}
|
||||
|
||||
public override int GetAmountPerGame()
|
||||
{
|
||||
return (int)OptionGroupSingleton<CaptainModifierSettings>.Instance.Amount;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -16,7 +15,7 @@ public class FreezeModifier : TimedModifier
|
||||
|
||||
public override void OnActivate()
|
||||
{
|
||||
if (Player?.AmOwner == true)
|
||||
if (Player.AmOwner)
|
||||
{
|
||||
Player.moveable = false;
|
||||
}
|
||||
@@ -26,15 +25,15 @@ public class FreezeModifier : TimedModifier
|
||||
{
|
||||
base.FixedUpdate();
|
||||
|
||||
if (Player?.AmOwner == true || PlayerControl.LocalPlayer.Data.Role is FreezerRole)
|
||||
if (Player.AmOwner || PlayerControl.LocalPlayer.Data.Role is FreezerRole)
|
||||
{
|
||||
Player?.cosmetics.SetOutline(true, new Nullable<Color>(Palette.LightBlue));
|
||||
Player.cosmetics.SetOutline(true, new Nullable<Color>(Palette.LightBlue));
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnTimerComplete()
|
||||
{
|
||||
if (Player?.AmOwner == true)
|
||||
if (Player.AmOwner)
|
||||
{
|
||||
Player.moveable = true;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
|
||||
public class HighPriorityModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "High Priority";
|
||||
|
||||
public override int GetAssignmentChance()
|
||||
{
|
||||
return 100;
|
||||
|
||||
@@ -16,7 +16,7 @@ public class ExampleOptions : AbstractOptionGroup
|
||||
[ModdedToggleOption("Toggle Opt 2")]
|
||||
public bool ToggleOpt2 { get; set; } = true;
|
||||
|
||||
[ModdedNumberOption("Number Opt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Percent)]
|
||||
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
||||
public float NumberOpt { get; set; } = 4f;
|
||||
|
||||
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using MiraAPI.Example.Modifiers;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
|
||||
namespace MiraAPI.Example.Options.Modifiers;
|
||||
|
||||
public class CaptainModifierSettings : AbstractOptionGroup<CaptainModifier>
|
||||
{
|
||||
public override string GroupName => "Captain Settings";
|
||||
|
||||
[ModdedNumberOption("Amount", 0, 5)]
|
||||
public float Amount { get; set; } = 1;
|
||||
|
||||
[ModdedNumberOption("Chance", 0, 100, 10)]
|
||||
public float Chance { get; set; } = 50;
|
||||
|
||||
[ModdedNumberOption("Uses", 1, 5)]
|
||||
public float NumUses { get; set; } = 3;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using MiraAPI.Example.Modifiers.Freezer;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
|
||||
namespace MiraAPI.Example.Options.Modifiers;
|
||||
|
||||
public class FreezeModifierSettings : AbstractOptionGroup<FreezeModifier>
|
||||
{
|
||||
public override string GroupName => "Freeze";
|
||||
|
||||
[ModdedToggleOption("Use Color")]
|
||||
public bool UseColor { get; set; } = true;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
|
||||
namespace MiraAPI.Example.Options.Modifiers;
|
||||
|
||||
public class GeneralModifierOptions : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName => "General";
|
||||
|
||||
public override uint GroupPriority => 0;
|
||||
|
||||
public override bool ShowInModifiersMenu => true;
|
||||
|
||||
[ModdedToggleOption("Some Boolean Option")]
|
||||
public bool SomeBooleanOption { get; set; } = true;
|
||||
|
||||
[ModdedNumberOption("Number Option", 0, 10, 1)]
|
||||
public float NumberOption { get; set; } = 5;
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using System;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Example.Options.Roles;
|
||||
|
||||
public class FreezerRoleSettings : AbstractOptionGroup
|
||||
public class FreezerRoleSettings : AbstractOptionGroup<FreezerRole>
|
||||
{
|
||||
public override string GroupName => "Custom Role";
|
||||
|
||||
public override Type AdvancedRole => typeof(FreezerRole);
|
||||
|
||||
[ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
|
||||
public float FreezeDuration { get; set; } = 5;
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
@@ -7,12 +6,10 @@ using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Example.Options.Roles;
|
||||
|
||||
public class TeleporterOptions : AbstractOptionGroup
|
||||
public class TeleporterOptions : AbstractOptionGroup<TeleporterRole>
|
||||
{
|
||||
public override string GroupName => "Teleporter";
|
||||
|
||||
public override Type AdvancedRole => typeof(TeleporterRole);
|
||||
|
||||
public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds);
|
||||
|
||||
[ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)]
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using MiraAPI.Roles;
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -12,11 +14,18 @@ public class ChameloenRole : CrewmateRole, ICustomRole
|
||||
public Color RoleColor => Palette.AcceptedGreen;
|
||||
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
|
||||
|
||||
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
|
||||
public CustomRoleConfiguration Configuration => new(this)
|
||||
{
|
||||
OptionsScreenshot = ExampleAssets.Banner,
|
||||
IntroSound = CustomRoleUtils.GetIntroSound(RoleTypes.Shapeshifter),
|
||||
};
|
||||
|
||||
public override void Initialize(PlayerControl player)
|
||||
{
|
||||
Player = player;
|
||||
Logger<ExamplePlugin>.Error("Initializing ChamelonRole for player: " + player.PlayerId);
|
||||
}
|
||||
|
||||
public void PlayerControlFixedUpdate(PlayerControl playerControl)
|
||||
{
|
||||
if (playerControl.MyPhysics.Velocity.magnitude > 0)
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Roles;
|
||||
|
||||
public class MayorRole : CrewmateRole, ICustomRole
|
||||
{
|
||||
public string RoleName => "Mayor";
|
||||
public string RoleLongDescription => "You get extra votes!";
|
||||
public string RoleDescription => RoleLongDescription;
|
||||
public Color RoleColor => new Color32(221, 176, 152, 255);
|
||||
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
|
||||
public CustomRoleConfiguration Configuration => new(this);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Roles;
|
||||
@@ -22,7 +24,7 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
|
||||
|
||||
public TeamIntroConfiguration? IntroConfiguration { get; } = new(
|
||||
Color.gray,
|
||||
"NEUTRAL",
|
||||
"OUTCAST",
|
||||
"You are an Outcast. You do not have a team.");
|
||||
|
||||
public override void SpawnTaskHeader(PlayerControl playerControl)
|
||||
@@ -32,6 +34,6 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
|
||||
|
||||
public override bool DidWin(GameOverReason gameOverReason)
|
||||
{
|
||||
return GameManager.Instance.DidHumansWin(gameOverReason);
|
||||
return gameOverReason == CustomGameOver.GameOverReason<NeutralKillerGameOver>();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Events;
|
||||
|
||||
@@ -19,7 +21,7 @@ public static class MiraEventManager
|
||||
public static bool InvokeEvent<T>(T eventInstance) where T : MiraEvent
|
||||
{
|
||||
EventWrappers.TryGetValue(typeof(T), out var handlers);
|
||||
if (handlers == null)
|
||||
if (handlers == null || handlers.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -41,7 +43,7 @@ public static class MiraEventManager
|
||||
public static bool InvokeEvent(MiraEvent eventInstance, Type type)
|
||||
{
|
||||
EventWrappers.TryGetValue(type, out var handlers);
|
||||
if (handlers == null)
|
||||
if (handlers == null || handlers.Count == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -54,28 +56,56 @@ public static class MiraEventManager
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an event.
|
||||
/// </summary>
|
||||
/// <param name="type">The type of event.</param>
|
||||
/// <param name="methodInfo">The MethodInfo of the event handler.</param>
|
||||
/// <param name="priority">The priority of the event handler. Lower values are called first.</param>
|
||||
public static void RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Type must be a subclass of MiraEvent: {type.FullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
EventWrappers.TryAdd(type, []);
|
||||
var handlers = EventWrappers[type];
|
||||
|
||||
var @delegate = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(type), methodInfo);
|
||||
var eventWrapper = new MiraEventWrapper(@delegate, priority);
|
||||
|
||||
var index = handlers.BinarySearch(eventWrapper, Comparer<MiraEventWrapper>.Create((a, b) => a.Priority.CompareTo(b.Priority)));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
handlers.Insert(index, eventWrapper);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register an event.
|
||||
/// </summary>
|
||||
/// <param name="handler">The callback method/handler for the event.</param>
|
||||
/// <param name="priority">The priority of the event handler. Higher values are called first.</param>
|
||||
/// <param name="priority">The priority of the event handler. Lower values are called first.</param>
|
||||
/// <typeparam name="T">Type of event.</typeparam>
|
||||
public static void RegisterEventHandler<T>(Action<T> handler, int priority = 0) where T : MiraEvent
|
||||
{
|
||||
if (!EventWrappers.ContainsKey(typeof(T)))
|
||||
{
|
||||
EventWrappers.Add(typeof(T), []);
|
||||
}
|
||||
EventWrappers.TryAdd(typeof(T), []);
|
||||
|
||||
var handlers = EventWrappers[typeof(T)];
|
||||
handlers.Add(new MiraEventWrapper(handler, priority));
|
||||
}
|
||||
var eventWrapper = new MiraEventWrapper(handler, priority);
|
||||
|
||||
internal static void SortAllHandlers()
|
||||
{
|
||||
foreach (var handlers in EventWrappers.Values)
|
||||
var index = handlers.BinarySearch(eventWrapper, Comparer<MiraEventWrapper>.Create((a, b) => a.Priority.CompareTo(b.Priority)));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
handlers.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
handlers.Insert(index, eventWrapper);
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ public class MiraEventWrapper
|
||||
public Delegate EventHandler { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the priority of the handler. Higher values are called first.
|
||||
/// Gets the priority of the handler. Lower values are called first.
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Register an event.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class RegisterEventAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the priority of the event. Lower values are called first.
|
||||
/// </summary>
|
||||
public int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RegisterEventAttribute"/> class.
|
||||
/// </summary>
|
||||
/// <param name="priority">The priority of the event. Lower values are called first.</param>
|
||||
public RegisterEventAttribute(int priority = 0)
|
||||
{
|
||||
Priority = priority;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when the intro cutscene has finished playing.
|
||||
/// </summary>
|
||||
public class IntroEndEvent(IntroCutscene introCutscene) : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the IntroCutscene.
|
||||
/// </summary>
|
||||
public IntroCutscene IntroCutscene { get; } = introCutscene;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp.
|
||||
/// </summary>
|
||||
public class RoundStartEvent : MiraEvent
|
||||
{
|
||||
/// <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="RoundStartEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
|
||||
public RoundStartEvent(bool triggeredByIntro)
|
||||
{
|
||||
TriggeredByIntro = triggeredByIntro;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
namespace MiraAPI.Events.Vanilla.Meeting;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when the meeting has ended.
|
||||
/// </summary>
|
||||
public class EndMeetingEvent(MeetingHud meetingHud) : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MeetingHud instance.
|
||||
/// </summary>
|
||||
public MeetingHud MeetingHud { get; } = meetingHud;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
namespace MiraAPI.Events.Vanilla.Meeting;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when a body is reported.
|
||||
/// This is invoked in CmdReportBody.
|
||||
/// </summary>
|
||||
public class ReportBodyEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player that is reporting the body.
|
||||
/// </summary>
|
||||
public PlayerControl Reporter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player that is being reported. Will be null for emergency meeting.
|
||||
/// </summary>
|
||||
public NetworkedPlayerInfo? Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the body that is being reported. Will be null for emergency meeting.
|
||||
/// </summary>
|
||||
public DeadBody? Body { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ReportBodyEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="reporter">The player who reported the body.</param>
|
||||
/// <param name="target">The player being reported.</param>
|
||||
/// <param name="body">The body being reported.</param>
|
||||
public ReportBodyEvent(PlayerControl reporter, NetworkedPlayerInfo? target, DeadBody? body)
|
||||
{
|
||||
Reporter = reporter;
|
||||
Target = target;
|
||||
Body = body;
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,22 @@
|
||||
namespace MiraAPI.Events.Vanilla.Meeting;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when a meeting is called.
|
||||
/// The event that is invoked when a meeting is called. This event is not cancelable.
|
||||
/// This event is called after Mira resets votes, so if you plan on adding votes to a specific player, do it with this event.
|
||||
/// </summary>
|
||||
public class StartMeetingEvent : MiraCancelableEvent
|
||||
public class StartMeetingEvent : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player that is calling the meeting.
|
||||
/// Gets the MeetingHud instance.
|
||||
/// </summary>
|
||||
public PlayerControl Reporter { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player that is being reported. Will be null for emergency meeting.
|
||||
/// </summary>
|
||||
public NetworkedPlayerInfo? Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the body that is being reported. Will be null for emergency meeting.
|
||||
/// </summary>
|
||||
public DeadBody? Body { get; }
|
||||
public MeetingHud MeetingHud { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="StartMeetingEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="reporter">The player who reported the body.</param>
|
||||
/// <param name="target">The player being reported.</param>
|
||||
/// <param name="body">The body being reported.</param>
|
||||
public StartMeetingEvent(PlayerControl reporter, NetworkedPlayerInfo? target, DeadBody? body)
|
||||
/// <param name="meetingHud">The MeetingHud instance.</param>
|
||||
public StartMeetingEvent(MeetingHud meetingHud)
|
||||
{
|
||||
Reporter = reporter;
|
||||
Target = target;
|
||||
Body = body;
|
||||
MeetingHud = meetingHud;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if voting is complete. If canceled, the default end voting logic will be skipped.
|
||||
/// </summary>
|
||||
public class CheckForEndVotingEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether default voting logic determines that voting is complete.
|
||||
/// </summary>
|
||||
public bool IsVotingComplete { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the voting should be forced to end, regardless of the default logic.
|
||||
/// </summary>
|
||||
public bool ForceEndVoting { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CheckForEndVotingEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="isVotingComplete">>True if voting is complete, false otherwise.</param>
|
||||
public CheckForEndVotingEvent(bool isVotingComplete)
|
||||
{
|
||||
IsVotingComplete = isVotingComplete;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when Mira handles a vote. This is invoked before Mira's behaviour, so be cautious.
|
||||
/// If you intend on adding custom vote behaviour, cancel the event and do so.
|
||||
/// If you are NOT canceling the event, keep in mind that Mira automatically removes a vote and adds the suspect to the VotedPlayers list after this event is invoked.
|
||||
/// </summary>
|
||||
public class HandleVoteEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the voter's vote data.
|
||||
/// </summary>
|
||||
public PlayerVoteData VoteData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player who voted.
|
||||
/// </summary>
|
||||
public PlayerControl Player { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player id of the target.
|
||||
/// </summary>
|
||||
public byte TargetId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player info of the target.
|
||||
/// </summary>
|
||||
public NetworkedPlayerInfo TargetPlayerInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="HandleVoteEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteData">The voter's data.</param>
|
||||
/// <param name="targetId">The target's playerId.</param>
|
||||
public HandleVoteEvent(PlayerVoteData playerVoteData, byte targetId)
|
||||
{
|
||||
VoteData = playerVoteData;
|
||||
Player = playerVoteData.Owner;
|
||||
TargetId = targetId;
|
||||
TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when the player tries to select another player to vote.
|
||||
/// </summary>
|
||||
public class MeetingSelectEvent : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the voter's vote data.
|
||||
/// </summary>
|
||||
public PlayerVoteData VoteData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player id of the target.
|
||||
/// </summary>
|
||||
public int TargetId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player info of the target.
|
||||
/// </summary>
|
||||
public NetworkedPlayerInfo TargetPlayerInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the player is allowed to select the target.
|
||||
/// </summary>
|
||||
public bool AllowSelect { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MeetingSelectEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteData">The voter's data.</param>
|
||||
/// <param name="targetId">The target's playerId.</param>
|
||||
/// <param name="allowSelect">>Whether the player is allowed to select the target.</param>
|
||||
public MeetingSelectEvent(PlayerVoteData playerVoteData, int targetId, bool allowSelect)
|
||||
{
|
||||
VoteData = playerVoteData;
|
||||
TargetId = targetId;
|
||||
TargetPlayerInfo = GameData.Instance.GetPlayerById((byte)targetId);
|
||||
AllowSelect = allowSelect;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Called in VotingUtils.HandlePopulateResults.
|
||||
/// Cancelling is NOT advised but if you do plan on cancelling, please ensure you account for displaying ALL player's votes.
|
||||
/// </summary>
|
||||
public class PopulateResultsEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of networked votes.
|
||||
/// </summary>
|
||||
public List<CustomVote> Votes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PopulateResultsEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="votes">The list of votes.</param>
|
||||
public PopulateResultsEvent(List<CustomVote> votes)
|
||||
{
|
||||
Votes = votes;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Ran after calculating votes and before displaying the results. Only ran on the host.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Initializes a new instance of the <see cref="ProcessVotesEvent"/> class.
|
||||
/// </remarks>
|
||||
/// <param name="votes">>The list of votes that are being processed.</param>
|
||||
/// <param name="exiledPlayer">The player to be exiled. Will be null if no player is to be exiled.</param>
|
||||
public class ProcessVotesEvent(List<CustomVote> votes, NetworkedPlayerInfo? exiledPlayer = null) : MiraEvent
|
||||
{
|
||||
private readonly List<CustomVote> _originalVotes = [.. votes];
|
||||
|
||||
private NetworkedPlayerInfo? _exiledPlayer = exiledPlayer;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the exiled player has been modified by the event.
|
||||
/// </summary>
|
||||
public bool ExiledPlayerModified { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the votes have been modified by the event.
|
||||
/// </summary>
|
||||
public bool VotesModified => !Votes.SequenceEqual(_originalVotes);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of votes that are being processed.
|
||||
/// </summary>
|
||||
public List<CustomVote> Votes { get; } = votes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the player to be exiled. Will be null if no player is to be exiled.
|
||||
/// </summary>
|
||||
public NetworkedPlayerInfo? ExiledPlayer
|
||||
{
|
||||
get => _exiledPlayer;
|
||||
set
|
||||
{
|
||||
_exiledPlayer = value;
|
||||
ExiledPlayerModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// The event that is invoked when the voting phase of a meeting is complete.
|
||||
/// </summary>
|
||||
/// <param name="meetingHud">The MeetingHud instance.</param>
|
||||
public class VotingCompleteEvent(MeetingHud meetingHud) : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the MeetingHud instance.
|
||||
/// </summary>
|
||||
public MeetingHud MeetingHud { get; } = meetingHud;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace MiraAPI.Events.Vanilla;
|
||||
namespace MiraAPI.Events.Vanilla.Usables;
|
||||
|
||||
/// <summary>
|
||||
/// Event for <see cref="PlayerControl"/> using a <see cref="IUsable"/> from Vanilla Among Us. Will always be ran locally.
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Collections.Generic;
|
||||
using MiraAPI.Networking;
|
||||
using Reactor.Networking.Rpc;
|
||||
|
||||
namespace MiraAPI.GameEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Custom Game Over.
|
||||
/// </summary>
|
||||
public abstract class CustomGameOver
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current CustomGameOver.
|
||||
/// </summary>
|
||||
public static CustomGameOver? Instance { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Verifies if the condition for this CustomGameOver is met.
|
||||
/// </summary>
|
||||
/// <param name="playerControl">The PlayerControl that requested the Game Over.</param>
|
||||
/// <param name="winners">The collection of winners.</param>
|
||||
/// <returns>True if the condition is met, otherwise false.</returns>
|
||||
public virtual bool VerifyCondition(PlayerControl playerControl, NetworkedPlayerInfo[] winners)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs before the base game calls EndGameManager.SetEverythingUp.
|
||||
/// </summary>
|
||||
/// <param name="endGameManager">The EndGameManager instance.</param>
|
||||
/// <returns>Return True to use the run the original method, return False to skip it.</returns>
|
||||
public virtual bool BeforeEndGameSetup(EndGameManager endGameManager)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs after the base game calls EndGameManager.SetEverythingUp.
|
||||
/// </summary>
|
||||
/// <param name="endGameManager">The EndGameManager instance.</param>
|
||||
public virtual void AfterEndGameSetup(EndGameManager endGameManager)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the GameOverReason associated with this CustomGameOver.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the custom game over.</typeparam>
|
||||
/// <returns>The GameOverReason associated with the custom game over.</returns>
|
||||
public static GameOverReason GameOverReason<T>() where T : CustomGameOver
|
||||
{
|
||||
return (GameOverReason)GameOverManager.GetGameOverId<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a custom game over.
|
||||
/// </summary>
|
||||
/// <param name="winners">A collection of winners.</param>
|
||||
/// <typeparam name="T">Type of the custom game over.</typeparam>
|
||||
public static void Trigger<T>(IEnumerable<NetworkedPlayerInfo> winners) where T : CustomGameOver
|
||||
{
|
||||
var reason = GameOverManager.GetGameOverId<T>();
|
||||
var data = new GameOverData(reason, [.. winners]);
|
||||
|
||||
Rpc<CustomGameOverRpc>.Instance.Send(PlayerControl.LocalPlayer, data, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly convert a CustomGameOver to a GameOverReason.
|
||||
/// </summary>
|
||||
/// <param name="gameOver">The CustomGameOver instance.</param>
|
||||
/// <returns>The GameOverReason associated with the CustomGameOver.</returns>
|
||||
public static implicit operator GameOverReason(CustomGameOver gameOver)
|
||||
{
|
||||
return (GameOverReason)GameOverManager.GetGameOverId(gameOver.GetType());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MiraAPI.GameEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Data for a game over.
|
||||
/// </summary>
|
||||
/// <param name="Reason">Reason for the game over.</param>
|
||||
/// <param name="Winners">A collection of winners.</param>
|
||||
public record struct GameOverData(int Reason, List<NetworkedPlayerInfo> Winners);
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.GameEnd;
|
||||
|
||||
/// <summary>
|
||||
/// Manage custom game overs.
|
||||
/// </summary>
|
||||
public static class GameOverManager
|
||||
{
|
||||
private static readonly Dictionary<Type, int> GameOverIds = [];
|
||||
private static readonly Dictionary<int, Type> GameOverTypes = [];
|
||||
|
||||
private static int _nextId = Enum.GetNames<GameOverReason>().Length;
|
||||
|
||||
/// <summary>
|
||||
/// Register a custom game over.
|
||||
/// </summary>
|
||||
/// <param name="gameOverType">Type of the custom game over.</param>
|
||||
/// <exception cref="ArgumentException">Thrown when the type is not a subclass of CustomGameOver, is abstract, or does not have a parameterless constructor.</exception>
|
||||
/// <returns>>True if the game over was registered successfully, false otherwise.</returns>
|
||||
public static bool RegisterGameOver(Type gameOverType)
|
||||
{
|
||||
if (!typeof(CustomGameOver).IsAssignableFrom(gameOverType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameOverType.IsAbstract)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("The type must not be abstract.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameOverType.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("The type must have a parameterless constructor.");
|
||||
return false;
|
||||
}
|
||||
|
||||
GameOverIds.Add(gameOverType, _nextId);
|
||||
GameOverTypes.Add(_nextId, gameOverType);
|
||||
_nextId++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create an instance of a custom game over.
|
||||
/// </summary>
|
||||
/// <param name="id">ID of the custom game over.</param>
|
||||
/// <param name="customGameOver">The created instance of the custom game over.</param>
|
||||
/// <returns>An instance of the custom game over.</returns>
|
||||
public static bool TryGetGameOver(int id, [NotNullWhen(true)] out CustomGameOver? customGameOver)
|
||||
{
|
||||
if (GameOverTypes.TryGetValue(id, out var gameOverType))
|
||||
{
|
||||
customGameOver = Activator.CreateInstance(gameOverType) as CustomGameOver;
|
||||
return customGameOver != null;
|
||||
}
|
||||
|
||||
customGameOver = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the ID of a custom game over.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">Type of the custom game over.</typeparam>
|
||||
/// <returns>The ID of the custom game over.</returns>
|
||||
public static int GetGameOverId<T>() where T : CustomGameOver
|
||||
{
|
||||
return GetGameOverId(typeof(T));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the ID of a custom game over.
|
||||
/// </summary>
|
||||
/// <param name="gameOverType">Type of the custom game over.</param>
|
||||
/// <returns>The ID of the custom game over.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the type is not registered.</exception>
|
||||
public static int GetGameOverId(Type gameOverType)
|
||||
{
|
||||
if (GameOverIds.TryGetValue(gameOverType, out var id))
|
||||
return id;
|
||||
|
||||
throw new ArgumentException($"{gameOverType.FullName} is not a registered custom game over!");
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.GameModes;
|
||||
|
||||
|
||||
@@ -16,6 +16,17 @@ public abstract class AbstractOptionGroup
|
||||
/// </summary>
|
||||
public abstract string GroupName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Optionable type of the group.
|
||||
/// </summary>
|
||||
public virtual Type? OptionableType => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the group should be shown in the modifiers menu.
|
||||
/// </summary>
|
||||
// TODO: make this not a boolean
|
||||
public virtual bool ShowInModifiersMenu => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function that determines whether the group should be visible or not.
|
||||
/// </summary>
|
||||
@@ -24,7 +35,7 @@ public abstract class AbstractOptionGroup
|
||||
/// <summary>
|
||||
/// Gets the group color. This is used to color the group in the options menu.
|
||||
/// </summary>
|
||||
public virtual Color GroupColor => Color.clear;
|
||||
public virtual Color GroupColor => MiraApiPlugin.DefaultHeaderColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the group priority. This is used to determine the order in which groups are displayed in the options menu.
|
||||
@@ -32,12 +43,17 @@ public abstract class AbstractOptionGroup
|
||||
/// </summary>
|
||||
public virtual uint GroupPriority => uint.MaxValue;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the role the group is associated with. This is used for the advanced role options menu.
|
||||
/// </summary>
|
||||
public virtual Type? AdvancedRole => null;
|
||||
|
||||
internal bool AllOptionsHidden { get; set; }
|
||||
|
||||
internal CategoryHeaderMasked? Header { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the optionable that this group contains.</typeparam>
|
||||
public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOptionable
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type OptionableType => typeof(T);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
@@ -8,12 +8,12 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
/// Attribute for creating an enum option.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null, Type? roleType = null)
|
||||
: ModdedOptionAttribute(title, roleType)
|
||||
public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null)
|
||||
: ModdedOptionAttribute(title)
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values, RoleType);
|
||||
var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values);
|
||||
return opt;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Utilities;
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
@@ -15,13 +15,13 @@ public class ModdedNumberOptionAttribute(
|
||||
float max,
|
||||
float increment = 1,
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
|
||||
bool zeroInfinity = false,
|
||||
Type? roleType = null)
|
||||
: ModdedOptionAttribute(title, roleType)
|
||||
string? formatString = null,
|
||||
bool zeroInfinity = false)
|
||||
: ModdedOptionAttribute(title)
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, zeroInfinity, RoleType);
|
||||
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using System;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
@@ -8,13 +8,12 @@ namespace MiraAPI.GameOptions.Attributes;
|
||||
/// Attribute for a toggle option.
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="roleType">An optional role type.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedToggleOptionAttribute(string title, Type? roleType = null) : ModdedOptionAttribute(title, roleType)
|
||||
public class ModdedToggleOptionAttribute(string title) : ModdedOptionAttribute(title)
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false), RoleType);
|
||||
var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false));
|
||||
return toggleOpt;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ public interface IModdedOption
|
||||
StringNames StringName { get; }
|
||||
IMiraPlugin? ParentMod { get; set; }
|
||||
BaseGameSetting Data { get; }
|
||||
Type? AdvancedRole { get; set; }
|
||||
OptionBehaviour? OptionBehaviour { get; }
|
||||
Func<bool> Visible { get; set; }
|
||||
ConfigDefinition? ConfigDefinition { get; set; }
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for classes that can be used for option groups.
|
||||
/// </summary>
|
||||
public interface IOptionable;
|
||||
@@ -108,7 +108,7 @@ public static class ModdedOptionsManager
|
||||
RegisterOption(option, group, property.Name, pluginInfo);
|
||||
}
|
||||
|
||||
private static void RegisterOption(
|
||||
internal static void RegisterOption(
|
||||
IModdedOption option,
|
||||
AbstractOptionGroup group,
|
||||
string propertyName,
|
||||
@@ -119,10 +119,7 @@ public static class ModdedOptionsManager
|
||||
option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
|
||||
|
||||
option.ParentMod = pluginInfo.MiraPlugin;
|
||||
option.AdvancedRole = group.AdvancedRole;
|
||||
|
||||
pluginInfo.Options.Add(option);
|
||||
|
||||
ModdedOptions.Add(option.Id, option);
|
||||
group.Options.Add(option);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
internal class ModifierOptionGroup : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName { get; }
|
||||
|
||||
public ModifierOptionGroup(string name, IModdedOption[] options, params AbstractOptionGroup[] groups)
|
||||
{
|
||||
GroupName = name;
|
||||
Options.AddRange(options);
|
||||
Options.AddRange(groups.SelectMany(x=>x.Options));
|
||||
}
|
||||
}
|
||||
@@ -27,8 +27,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="enumType">The Enum type.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
/// <param name="roleType">An optional role type to specify for a specific role.</param>
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType)
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(enumType);
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
@@ -101,7 +100,6 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// An option for selecting an enum value.
|
||||
/// </summary>
|
||||
@@ -118,10 +116,8 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="enumType">The Enum type.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
/// <param name="roleType">An optional role type to specify for a specific role.</param>
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType)
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(typeof(T));
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
@@ -147,7 +143,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
||||
stringOption.Title = StringName;
|
||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||
stringOption.Value = Convert.ToInt32(Value);
|
||||
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
|
||||
OptionBehaviour = stringOption;
|
||||
|
||||
@@ -157,13 +153,13 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
return Convert.ToSingle(Value);
|
||||
return Convert.ToSingle(Value, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override NetData GetNetData()
|
||||
{
|
||||
return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value)));
|
||||
return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value, NumberFormatInfo.InvariantInfo)!));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -175,13 +171,13 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <inheritdoc />
|
||||
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString());
|
||||
return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(T newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(Convert.ToInt32(newValue)), false);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
@@ -189,7 +185,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = Convert.ToInt32(newValue);
|
||||
opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System;
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@@ -46,8 +46,8 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// <param name="max">The maximum value.</param>
|
||||
/// <param name="increment">The increment.</param>
|
||||
/// <param name="suffixType">The suffix type.</param>
|
||||
/// <param name="formatString">Optional format string for the option screen.</param>
|
||||
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
|
||||
/// <param name="roleType">An optional role type.</param>
|
||||
public ModdedNumberOption(
|
||||
string title,
|
||||
float defaultValue,
|
||||
@@ -55,8 +55,8 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
float max,
|
||||
float increment,
|
||||
MiraNumberSuffixes suffixType,
|
||||
bool zeroInfinity = false,
|
||||
Type? roleType = null) : base(title, defaultValue, roleType)
|
||||
string? formatString = null,
|
||||
bool zeroInfinity = false) : base(title, defaultValue)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
@@ -74,7 +74,13 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
data.Value = Value;
|
||||
data.Increment = Increment;
|
||||
data.ValidRange = new FloatRange(Min, Max);
|
||||
data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0"; // not sure if the == checks will work since this is floats but we'll find out
|
||||
data.FormatString = formatString ??
|
||||
(defaultValue.IsInteger() &&
|
||||
Increment.IsInteger() &&
|
||||
Value.IsInteger() &&
|
||||
Min.IsInteger() &&
|
||||
Max.IsInteger() ? "0" : "0.0");
|
||||
|
||||
data.ZeroIsInfinity = ZeroInfinity;
|
||||
data.SuffixType = (NumberSuffixes)SuffixType;
|
||||
data.OptionName = FloatOptionNames.Invalid;
|
||||
@@ -136,7 +142,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
Value = Mathf.Clamp(newValue, Min, Max);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(
|
||||
StringName,
|
||||
Data?.GetValueString(Value),
|
||||
Data.GetValueString(Value),
|
||||
false);
|
||||
|
||||
if (OptionBehaviour is NumberOption opt)
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
using BepInEx.Configuration;
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Networking.Rpc;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
@@ -72,11 +71,6 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// </summary>
|
||||
public Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the advanced role of the option.
|
||||
/// </summary>
|
||||
public Type? AdvancedRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option behaviour of the option.
|
||||
/// </summary>
|
||||
@@ -85,25 +79,14 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <summary>
|
||||
/// Gets or sets the config definition of the option.
|
||||
/// </summary>
|
||||
public ConfigDefinition? ConfigDefinition
|
||||
{
|
||||
get => _configDefinition;
|
||||
set
|
||||
{
|
||||
if (_configDefinition is not null) return;
|
||||
_configDefinition = value;
|
||||
}
|
||||
}
|
||||
|
||||
private ConfigDefinition? _configDefinition;
|
||||
public ConfigDefinition? ConfigDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedOption{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="roleType">The Role Type or null if it doesn't belong to a role.</param>
|
||||
protected ModdedOption(string title, T defaultValue, Type? roleType)
|
||||
protected ModdedOption(string title, T defaultValue)
|
||||
{
|
||||
Id = ModdedOptionsManager.NextId;
|
||||
Title = title;
|
||||
@@ -111,11 +94,6 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
Value = defaultValue;
|
||||
StringName = CustomStringName.CreateAndRegister(Title);
|
||||
Visible = () => true;
|
||||
|
||||
if (roleType is not null && roleType.IsAssignableTo(typeof(ICustomRole)))
|
||||
{
|
||||
AdvancedRole = roleType;
|
||||
}
|
||||
}
|
||||
|
||||
internal void ValueChanged(OptionBehaviour optionBehaviour)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using MiraAPI.Networking;
|
||||
using System;
|
||||
using System;
|
||||
using MiraAPI.Networking;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@@ -15,8 +15,7 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="roleType">An optional role type.</param>
|
||||
public ModdedToggleOption(string title, bool defaultValue, Type? roleType = null) : base(title, defaultValue, roleType)
|
||||
public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue)
|
||||
{
|
||||
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
using MiraAPI.Events;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.Patches;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
@@ -46,9 +47,14 @@ public abstract class CustomActionButton
|
||||
public virtual int MaxUses => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the location of the button on the screen.
|
||||
/// Gets the button's text outline color.
|
||||
/// </summary>
|
||||
public virtual ButtonLocation Location { get; private set; } = ButtonLocation.BottomLeft;
|
||||
public virtual Color TextOutlineColor => Color.clear;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the location of the button on the screen.
|
||||
/// </summary>
|
||||
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the button has an effect ability.
|
||||
@@ -63,27 +69,27 @@ public abstract class CustomActionButton
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
|
||||
/// </summary>
|
||||
public bool EffectActive { get; protected set; }
|
||||
public bool EffectActive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the timer is currently active.
|
||||
/// </summary>
|
||||
public bool TimerPaused { get; protected set; }
|
||||
public bool TimerPaused { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the amount of uses left.
|
||||
/// </summary>
|
||||
public int UsesLeft { get; protected set; }
|
||||
public int UsesLeft { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the timer variable to measure cooldowns and effects.
|
||||
/// </summary>
|
||||
public float Timer { get; protected set; }
|
||||
public float Timer { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button object in game. This is created by Mira API automatically.
|
||||
/// Gets or sets the button object in game. This is created by Mira API automatically.
|
||||
/// </summary>
|
||||
protected ActionButton? Button { get; private set; }
|
||||
public ActionButton? Button { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The method used to create the button.
|
||||
@@ -113,6 +119,11 @@ public abstract class CustomActionButton
|
||||
Button.SetInfiniteUses();
|
||||
}
|
||||
|
||||
if (TextOutlineColor != Color.clear)
|
||||
{
|
||||
SetTextOutline(TextOutlineColor);
|
||||
}
|
||||
|
||||
var pb = Button.GetComponent<PassiveButton>();
|
||||
pb.OnClick = new Button.ButtonClickedEvent();
|
||||
pb.OnClick.AddListener((UnityAction)(() =>
|
||||
@@ -157,7 +168,7 @@ public abstract class CustomActionButton
|
||||
/// Allows you to change the button's location.
|
||||
/// </summary>
|
||||
/// <param name="location">The new location.</param>
|
||||
/// <param name="moveButton">Whether the button's position should change ingame.</param>
|
||||
/// <param name="moveButton">Whether the button's position should change in-game.</param>
|
||||
public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true)
|
||||
{
|
||||
if (!HudManager.InstanceExists || Button == null)
|
||||
@@ -206,6 +217,15 @@ public abstract class CustomActionButton
|
||||
EffectActive = false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A utility function to change the outline color of the button's text.
|
||||
/// </summary>
|
||||
/// <param name="color">The new color.</param>
|
||||
public virtual void SetTextOutline(Color color)
|
||||
{
|
||||
Button?.buttonLabelText.SetOutlineColor(color);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A utility function to override the sprite of the button.
|
||||
/// </summary>
|
||||
@@ -343,7 +363,7 @@ public abstract class CustomActionButton
|
||||
/// <returns>A value that represents whether the button should light up or not.</returns>
|
||||
public virtual bool CanUse()
|
||||
{
|
||||
return !LimitedUses || UsesLeft > 0;
|
||||
return PlayerControl.LocalPlayer.moveable && (!LimitedUses || UsesLeft > 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -397,9 +417,12 @@ public abstract class CustomActionButton
|
||||
/// <param name="playerControl">The local PlayerControl.</param>
|
||||
public virtual void FixedUpdateHandler(PlayerControl playerControl)
|
||||
{
|
||||
if (Timer >= 0 && !TimerPaused)
|
||||
if (Timer >= 0)
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
if (!TimerPaused)
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
}
|
||||
}
|
||||
else if (HasEffect && EffectActive)
|
||||
{
|
||||
@@ -408,25 +431,28 @@ public abstract class CustomActionButton
|
||||
OnEffectEnd();
|
||||
}
|
||||
|
||||
if (CanUse())
|
||||
if (Button)
|
||||
{
|
||||
Button?.SetEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Button?.SetDisabled();
|
||||
}
|
||||
if (CanUse())
|
||||
{
|
||||
Button.SetEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetDisabled();
|
||||
}
|
||||
|
||||
if (EffectActive)
|
||||
{
|
||||
Button?.SetFillUp(Timer, EffectDuration);
|
||||
if (EffectActive)
|
||||
{
|
||||
Button.SetFillUp(Timer, EffectDuration);
|
||||
|
||||
Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString();
|
||||
Button!.cooldownTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Button?.SetCoolDown(Timer, Cooldown);
|
||||
Button.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
|
||||
Button.cooldownTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetCoolDown(Timer, Cooldown);
|
||||
}
|
||||
}
|
||||
|
||||
FixedUpdate(playerControl);
|
||||
@@ -442,7 +468,7 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
|
||||
/// <summary>
|
||||
/// Gets or sets the target object of the button.
|
||||
/// </summary>
|
||||
public T? Target { get; protected set; }
|
||||
public T? Target { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the distance the player must be from the target object to use the button.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.PluginLoading;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.PluginLoading;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Patches.Stubs;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
@@ -16,22 +19,18 @@ namespace MiraAPI.Hud;
|
||||
/// </summary>
|
||||
/// <param name="il2CppPtr">Used by Il2Cpp. Do not use constructor, this is a MonoBehaviour.</param>
|
||||
[RegisterInIl2Cpp]
|
||||
[SuppressMessage("Design", "CA1051:Do not declare visible instance fields", Justification = "Unity Convention")]
|
||||
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1307:Accessible fields should begin with upper-case letter", Justification = "Unity Convention")]
|
||||
[SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Unity Convention")]
|
||||
public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
|
||||
{
|
||||
public ShapeshifterPanel PanelPrefab;
|
||||
|
||||
public float XStart = -0.8f;
|
||||
|
||||
public float YStart = 2.15f;
|
||||
|
||||
public float XOffset = 1.95f;
|
||||
|
||||
public float YOffset = -0.65f;
|
||||
|
||||
public UiElement BackButton;
|
||||
|
||||
public UiElement DefaultButtonSelected;
|
||||
|
||||
public ShapeshifterPanel panelPrefab;
|
||||
public float xStart = -0.8f;
|
||||
public float yStart = 2.15f;
|
||||
public float xOffset = 1.95f;
|
||||
public float yOffset = -0.65f;
|
||||
public UiElement backButton;
|
||||
public UiElement defaultButtonSelected;
|
||||
public List<ShapeshifterPanel> potentialVictims;
|
||||
|
||||
/// <summary>
|
||||
@@ -42,17 +41,17 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
|
||||
{
|
||||
var shapeShifterRole = RoleManager.Instance.GetRole(RoleTypes.Shapeshifter);
|
||||
|
||||
var ogMenu = shapeShifterRole.TryCast<ShapeshifterRole>().ShapeshifterMenu;
|
||||
var ogMenu = shapeShifterRole.TryCast<ShapeshifterRole>()!.ShapeshifterMenu;
|
||||
var newMenu = Instantiate(ogMenu);
|
||||
var customMenu = newMenu.gameObject.AddComponent<CustomPlayerMenu>();
|
||||
|
||||
customMenu.PanelPrefab = newMenu.PanelPrefab;
|
||||
customMenu.XStart = newMenu.XStart;
|
||||
customMenu.YStart = newMenu.YStart;
|
||||
customMenu.XOffset = newMenu.XOffset;
|
||||
customMenu.YOffset = newMenu.YOffset;
|
||||
customMenu.BackButton = newMenu.BackButton;
|
||||
var back = customMenu.BackButton.GetComponent<PassiveButton>();
|
||||
customMenu.panelPrefab = newMenu.PanelPrefab;
|
||||
customMenu.xStart = newMenu.XStart;
|
||||
customMenu.yStart = newMenu.YStart;
|
||||
customMenu.xOffset = newMenu.XOffset;
|
||||
customMenu.yOffset = newMenu.YOffset;
|
||||
customMenu.backButton = newMenu.BackButton;
|
||||
var back = customMenu.backButton.GetComponent<PassiveButton>();
|
||||
back.OnClick.RemoveAllListeners();
|
||||
back.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
@@ -65,7 +64,7 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
|
||||
|
||||
newMenu.DestroyImmediate();
|
||||
|
||||
customMenu.transform.SetParent(Camera.main.transform, false);
|
||||
customMenu.transform.SetParent(Camera.main!.transform, false);
|
||||
customMenu.transform.localPosition = new Vector3(0f, 0f, -50f);
|
||||
return customMenu;
|
||||
}
|
||||
@@ -75,6 +74,7 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
|
||||
ControllerManager.Instance.CloseOverlayMenu(name);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Begin(PlayerTask task)
|
||||
{
|
||||
throw new NotImplementedException("Use the other Begin method.");
|
||||
@@ -90,7 +90,7 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
|
||||
{
|
||||
MinigameStubs.Begin(this, null);
|
||||
|
||||
var back = BackButton.GetComponent<PassiveButton>();
|
||||
var back = backButton.GetComponent<PassiveButton>();
|
||||
back.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
onClick(null);
|
||||
@@ -107,13 +107,13 @@ public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
|
||||
var num = i % 3;
|
||||
var num2 = i / 3;
|
||||
var flag = PlayerControl.LocalPlayer.Data.Role.NameColor == player.Data.Role.NameColor;
|
||||
var shapeshifterPanel = Instantiate(PanelPrefab, transform);
|
||||
shapeshifterPanel.transform.localPosition = new Vector3(XStart + num * XOffset, YStart + num2 * YOffset, -1f);
|
||||
var shapeshifterPanel = Instantiate(panelPrefab, transform);
|
||||
shapeshifterPanel.transform.localPosition = new Vector3(xStart + num * xOffset, yStart + num2 * yOffset, -1f);
|
||||
shapeshifterPanel.SetPlayer(i, player.Data, (Il2CppSystem.Action)(() => { onClick(player); }));
|
||||
shapeshifterPanel.NameText.color = flag ? player.Data.Role.NameColor : Color.white;
|
||||
potentialVictims.Add(shapeshifterPanel);
|
||||
list2.Add(shapeshifterPanel.Button);
|
||||
}
|
||||
ControllerManager.Instance.OpenOverlayMenu(name, BackButton, DefaultButtonSelected, list2);
|
||||
ControllerManager.Instance.OpenOverlayMenu(name, backButton, defaultButtonSelected, list2);
|
||||
}
|
||||
}
|
||||
@@ -10,17 +10,30 @@ using UnityEngine;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
/// <summary>
|
||||
/// The main plugin class for Mira API.
|
||||
/// </summary>
|
||||
[BepInAutoPlugin("mira.api", "MiraAPI")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
[BepInDependency(ModCompatibility.SubmergedId, BepInDependency.DependencyFlags.SoftDependency)]
|
||||
[ReactorModFlags(ModFlags.RequireOnAllClients)]
|
||||
public partial class MiraApiPlugin : BasePlugin
|
||||
{
|
||||
private static MiraPluginManager? PluginManager { get; set; }
|
||||
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
/// <summary>
|
||||
/// Gets the branding Mira API color.
|
||||
/// </summary>
|
||||
public static Color MiraColor { get; } = new Color32(238, 154, 112, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default color for headers in the options menu.
|
||||
/// </summary>
|
||||
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
||||
|
||||
private static MiraPluginManager? PluginManager { get; set; }
|
||||
internal Harmony Harmony { get; } = new(Id);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Load()
|
||||
{
|
||||
Harmony.PatchAll();
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Reflection;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
/// <summary>
|
||||
/// Mod compatibility tools.
|
||||
/// </summary>
|
||||
public static class ModCompatibility
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID for the Submerged mod.
|
||||
/// </summary>
|
||||
public const string SubmergedId = "Submerged";
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the Submerged mod is loaded.
|
||||
/// </summary>
|
||||
/// <param name="submergedAssembly">Submerged mod assembly if loaded, null otherwise.</param>
|
||||
/// <returns>True if the Submerged mod is loaded, false otherwise.</returns>
|
||||
public static bool SubmergedLoaded([NotNullWhen(true)] out Assembly? submergedAssembly)
|
||||
{
|
||||
var result = IL2CPPChainloader.Instance.Plugins.TryGetValue(SubmergedId, out var plugin);
|
||||
submergedAssembly = result ? plugin?.Instance.GetType().Assembly : null;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Represents the assignment configuration for a game modifier.
|
||||
/// </summary>
|
||||
/// <param name="DefaultAmount">The default amount of players that can have this modifier in a game.</param>
|
||||
/// <param name="DefaultChance">The default chance of the modifier being assigned to a player.</param>
|
||||
/// <param name="CreateAmountOption">Whether Mira should create an amount option for the modifier.</param>
|
||||
/// <param name="CreateChanceOption">Whether Mira should create a chance option for the modifier.</param>
|
||||
public record struct AssignmentConfiguration(int DefaultAmount, int DefaultChance, bool CreateAmountOption, bool CreateChanceOption);
|
||||
@@ -1,14 +1,18 @@
|
||||
namespace MiraAPI.Modifiers;
|
||||
using System;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.PluginLoading;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all modifiers.
|
||||
/// </summary>
|
||||
public abstract class BaseModifier
|
||||
public abstract class BaseModifier : IOptionable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the player that the modifier is attached to.
|
||||
/// </summary>
|
||||
public PlayerControl? Player { get; internal set; }
|
||||
public PlayerControl Player { get; internal set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier component that the modifier is attached to.
|
||||
@@ -16,9 +20,27 @@ public abstract class BaseModifier
|
||||
public ModifierComponent? ModifierComponent { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier id.
|
||||
/// Gets a value indicating whether the modifier has been initialized.
|
||||
/// </summary>
|
||||
public uint ModifierId { get; internal set; }
|
||||
public bool Initialized { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique ID of the modifier.
|
||||
/// </summary>
|
||||
public Guid UniqueId { get; internal set; } = Guid.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type ID of the modifier.
|
||||
/// </summary>
|
||||
public uint TypeId => ModifierManager.GetModifierTypeId(GetType()) ?? throw new InvalidOperationException("Modifier is not registered.");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent mod of the modifier.
|
||||
/// </summary>
|
||||
public MiraPluginInfo ParentMod => Array.Find(
|
||||
MiraPluginManager.Instance.RegisteredPlugins(),
|
||||
x => x.Modifiers.Exists(y => y.TypeId == TypeId)
|
||||
) ?? throw new InvalidOperationException("Modifier is not registered.");
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier name.
|
||||
@@ -30,6 +52,16 @@ public abstract class BaseModifier
|
||||
/// </summary>
|
||||
public virtual bool HideOnUi => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is shown in the freeplay menu.
|
||||
/// </summary>
|
||||
public virtual bool ShowInFreeplay => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is unique. If true, the player can only have one instance of this modifier.
|
||||
/// </summary>
|
||||
public virtual bool Unique => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if <see cref="HideOnUi"/> is true.
|
||||
/// </summary>
|
||||
@@ -72,6 +104,13 @@ public abstract class BaseModifier
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a meeting starts.
|
||||
/// </summary>
|
||||
public virtual void OnMeetingStart()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the player can vent.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -24,14 +24,14 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
/// Gets the active modifiers on the player.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
public ImmutableList<BaseModifier> ActiveModifiers => Modifiers.ToImmutableList();
|
||||
public ImmutableList<BaseModifier> ActiveModifiers { get; private set; } = ImmutableList<BaseModifier>.Empty;
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
private List<BaseModifier> Modifiers { get; set; } = [];
|
||||
|
||||
private PlayerControl? _player;
|
||||
private PlayerControl _player = null!;
|
||||
|
||||
private TextMeshPro? _modifierText;
|
||||
private TextMeshPro _modifierText = null!;
|
||||
|
||||
private readonly List<BaseModifier> _toRemove = [];
|
||||
|
||||
@@ -42,9 +42,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
_toRemove.AddRange(Modifiers);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
private void Awake()
|
||||
{
|
||||
_player = GetComponent<PlayerControl>();
|
||||
if (_player == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("ModifierComponent could not find PlayerControl on the same GameObject.");
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierExtensions.ModifierComponents.Add(_player, this);
|
||||
|
||||
Modifiers = [];
|
||||
|
||||
if (!_player.AmOwner)
|
||||
@@ -58,15 +67,20 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
foreach (var modifier in _toRemove)
|
||||
var removed = _toRemove.Count > 0;
|
||||
foreach (var modifier in _toRemove.ToArray())
|
||||
{
|
||||
_toRemove.Remove(modifier);
|
||||
modifier.OnDeactivate();
|
||||
Modifiers.Remove(modifier);
|
||||
}
|
||||
|
||||
foreach (var modifier in _toAdd)
|
||||
var added = _toAdd.Count > 0;
|
||||
foreach (var modifier in _toAdd.ToArray())
|
||||
{
|
||||
_toAdd.Remove(modifier);
|
||||
Modifiers.Add(modifier);
|
||||
modifier.Initialized = true;
|
||||
modifier.OnActivate();
|
||||
|
||||
if (modifier is TimedModifier { AutoStart: true } timer)
|
||||
@@ -75,17 +89,17 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
}
|
||||
|
||||
if (_toAdd.Count > 0 || _toRemove.Count > 0)
|
||||
if (removed || added)
|
||||
{
|
||||
if (_player?.AmOwner == true)
|
||||
if (_player.AmOwner && HudManager.InstanceExists)
|
||||
{
|
||||
HudManager.Instance?.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
HudManager.Instance.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
}
|
||||
_toAdd.Clear();
|
||||
_toRemove.Clear();
|
||||
|
||||
ActiveModifiers = Modifiers.ToImmutableList();
|
||||
}
|
||||
|
||||
foreach (var modifier in Modifiers)
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
}
|
||||
@@ -93,75 +107,217 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
|
||||
private void Update()
|
||||
{
|
||||
foreach (var modifier in Modifiers)
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
modifier.Update();
|
||||
}
|
||||
|
||||
if (!_modifierText || _player?.AmOwner == false)
|
||||
if (!_modifierText || !_player.AmOwner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi);
|
||||
|
||||
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
|
||||
|
||||
if (baseModifiers.Length != 0 && !MeetingHud.Instance)
|
||||
if (MeetingHud.Instance)
|
||||
{
|
||||
var stringBuild = new StringBuilder();
|
||||
foreach (var mod in baseModifiers)
|
||||
return;
|
||||
}
|
||||
|
||||
var showText = false;
|
||||
var builder = new StringBuilder("<b><size=130%>Modifiers:</b></size>\n");
|
||||
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
if (modifier.HideOnUi)
|
||||
{
|
||||
stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}");
|
||||
continue;
|
||||
}
|
||||
_modifierText!.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
|
||||
}
|
||||
else if (_modifierText!.text != string.Empty)
|
||||
{
|
||||
_modifierText.text = string.Empty;
|
||||
|
||||
showText = true;
|
||||
builder.AppendLine(modifier.GetHudString());
|
||||
}
|
||||
|
||||
_modifierText.text = showText ? builder.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// Remove this component from the global list when destroyed
|
||||
ModifierExtensions.ModifierComponents.Remove(_player);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// Gets a collection of modifiers by type.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>A collection of modifiers.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public IEnumerable<T> GetModifiers<T>(Func<T, bool>? predicate=null) where T : BaseModifier
|
||||
{
|
||||
return ActiveModifiers.OfType<T>().Where(x => predicate == null || predicate(x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a collection of modifiers by type.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>A collection of modifiers.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier(Type type)
|
||||
public IEnumerable<BaseModifier> GetModifiers(Type type, Func<BaseModifier, bool>? predicate=null)
|
||||
{
|
||||
var modifier = Modifiers.Find(x => x.GetType() == type);
|
||||
return ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x)));
|
||||
}
|
||||
|
||||
if (modifier is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
|
||||
return;
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets a collection of modifiers by type ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier's type ID.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>A collection of modifiers.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public IEnumerable<BaseModifier> GetModifiers(uint id, Func<BaseModifier, bool>? predicate=null)
|
||||
{
|
||||
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
|
||||
$"Cannot get modifier with id {id} because it is not registered.");
|
||||
|
||||
RemoveModifier(modifier);
|
||||
return GetModifiers(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryGetModifier<T>([NotNullWhen(true)] out T? modifier, Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
modifier = GetModifier(predicate);
|
||||
return modifier != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryGetModifier(Type type, [NotNullWhen(true)] out BaseModifier? modifier, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
modifier = GetModifier(type, predicate);
|
||||
return modifier != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier type ID.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryGetModifier(uint id, [NotNullWhen(true)] out BaseModifier? modifier, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
modifier = GetModifier(id, predicate);
|
||||
return modifier != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its unique ID.
|
||||
/// </summary>
|
||||
/// <param name="modifierGuid">The modifier unique ID.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool TryGetModifier(Guid modifierGuid, [NotNullWhen(true)] out BaseModifier? modifier)
|
||||
{
|
||||
modifier = GetModifier(modifierGuid);
|
||||
return modifier != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>The Modifier if it is found, null otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public T? GetModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return GetModifiers(predicate).FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>The Modifier if it is found, null otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? GetModifier(Type type, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return GetModifiers(type).FirstOrDefault(predicate ?? (_ => true));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a modifier by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier ID.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>The Modifier if it is found, null otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? GetModifier(uint id, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
|
||||
$"Cannot get modifier with id {id} because it is not registered.");
|
||||
|
||||
return GetModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a modifier by unique ID.
|
||||
/// </summary>
|
||||
/// <param name="modifierGuid">The modifier's unique ID.</param>
|
||||
/// <returns>The modifier if it is found, or null.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? GetModifier(Guid modifierGuid)
|
||||
{
|
||||
return ActiveModifiers.Find(x => x.UniqueId == modifierGuid);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The modifier type.</typeparam>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier<T>() where T : BaseModifier
|
||||
public void RemoveModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
RemoveModifier(typeof(T));
|
||||
RemoveModifier(typeof(T), x => predicate == null || predicate((T)x));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// </summary>
|
||||
/// <param name="modifierId">The modifier ID.</param>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier(uint modifierId)
|
||||
public void RemoveModifier(Type type, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
var modifier = Modifiers.Find(x => x.ModifierId == modifierId);
|
||||
var modifiers = ActiveModifiers.Where(x => x.GetType() == type && (predicate == null || predicate(x))).ToList();
|
||||
if (modifiers.Count > 1)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot remove modifier {type.Name} because there are multiple instances of that modifier.");
|
||||
}
|
||||
|
||||
var modifier = modifiers.FirstOrDefault();
|
||||
if (modifier is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not active.");
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -175,7 +331,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier(BaseModifier modifier)
|
||||
{
|
||||
if (!Modifiers.Contains(modifier))
|
||||
if (!ActiveModifiers.Contains(modifier))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
|
||||
return;
|
||||
@@ -184,6 +340,48 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
_toRemove.Add(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// </summary>
|
||||
/// <param name="typeId">The modifier's type ID.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier(uint typeId, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
var type = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
|
||||
$"Cannot remove modifier with id {typeId} because it is not registered.");
|
||||
RemoveModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// </summary>
|
||||
/// <param name="uniqueId">The modifier's unique ID.</param>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier(Guid uniqueId)
|
||||
{
|
||||
var modifier = GetModifier(uniqueId);
|
||||
if (modifier == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modifier to the player.
|
||||
/// </summary>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <typeparam name="T">The Type of the modifier.</typeparam>
|
||||
/// <returns>The new modifier.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public T? AddModifier<T>(params object[] args) where T : BaseModifier
|
||||
{
|
||||
return AddModifier(typeof(T), args) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modifier to the player.
|
||||
/// </summary>
|
||||
@@ -192,24 +390,32 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? AddModifier(BaseModifier modifier)
|
||||
{
|
||||
if (Modifiers.Contains(modifier))
|
||||
// TODO: Make a proper synchronization system.
|
||||
if (LobbyBehaviour.Instance)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
|
||||
Logger<MiraApiPlugin>.Warning($"Modifiers added in the lobby won't sync to new players!");
|
||||
}
|
||||
|
||||
var id = modifier.TypeId;
|
||||
if (modifier.Unique && ActiveModifiers.Find(x => x.TypeId == id) != null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {id}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
var modifierId = ModifierManager.GetModifierId(modifier.GetType());
|
||||
|
||||
if (modifierId == null)
|
||||
if (ActiveModifiers.Contains(modifier))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!");
|
||||
Logger<MiraApiPlugin>.Error($"Player already has this modifier!");
|
||||
return null;
|
||||
}
|
||||
|
||||
_toAdd.Add(modifier);
|
||||
modifier.Player = _player;
|
||||
modifier.ModifierComponent = this;
|
||||
modifier.ModifierId = modifierId.Value;
|
||||
if (modifier.UniqueId == Guid.Empty)
|
||||
{
|
||||
modifier.UniqueId = Guid.NewGuid();
|
||||
}
|
||||
return modifier;
|
||||
}
|
||||
|
||||
@@ -217,62 +423,147 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
/// Adds a modifier to the player.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <returns>The modifier that was added.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? AddModifier(Type type)
|
||||
public BaseModifier? AddModifier(Type type, params object[] args)
|
||||
{
|
||||
var modifierId = ModifierManager.GetModifierId(type);
|
||||
if (modifierId == null)
|
||||
BaseModifier? modifier;
|
||||
if (args.Length > 0)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is not registered.");
|
||||
return null;
|
||||
modifier = ModifierFactory.CreateInstance(type, args);
|
||||
}
|
||||
else
|
||||
{
|
||||
modifier = Activator.CreateInstance(type) as BaseModifier;
|
||||
if (modifier == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot add modifier {type.Name} because it is not a valid modifier.");
|
||||
}
|
||||
}
|
||||
|
||||
if (Modifiers.Find(x => x.ModifierId == modifierId) != null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Activator.CreateInstance(type) is not BaseModifier modifier)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
AddModifier(modifier);
|
||||
|
||||
return modifier;
|
||||
return AddModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a modifier to the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Type of the modifier.</typeparam>
|
||||
/// <returns>The new modifier.</returns>
|
||||
/// <param name="id">The ID of the modifier.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <returns>The modifier if it was created, or null if it failed.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public T? AddModifier<T>() where T : BaseModifier
|
||||
public BaseModifier? AddModifier(uint id, params object[] args)
|
||||
{
|
||||
return AddModifier(typeof(T)) as T;
|
||||
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
|
||||
$"Cannot add modifier with id {id} because it is not registered.");
|
||||
|
||||
return AddModifier(type, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active or queued modifier by its ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The Modifier ID.</param>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
public bool HasModifier(uint id)
|
||||
{
|
||||
return ActiveModifiers.Exists(x => x.ModifierId == id) || _toAdd.Exists(x => x.ModifierId == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active or queued modifier by its type.
|
||||
/// Checks if a player has an active modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
public bool HasModifier<T>()
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier<T>(Func<T, bool>? predicate=null) where T : BaseModifier
|
||||
{
|
||||
return ActiveModifiers.Exists(x => x is T) || _toAdd.Exists(x => x is T);
|
||||
return ActiveModifiers.Exists(x => x is T modifier && (predicate == null || predicate(modifier)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier(Type type, Func<BaseModifier, bool>? predicate=null)
|
||||
{
|
||||
return ActiveModifiers.Exists(x => x.GetType() == type && (predicate == null || predicate(x)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier's type ID.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <returns>True if the modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier(uint id, Func<BaseModifier, bool>? predicate=null)
|
||||
{
|
||||
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
|
||||
$"Cannot get modifier with id {id} because it is not registered.");
|
||||
|
||||
return HasModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its unique ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier's guid.</param>
|
||||
/// <returns>True if the modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier(Guid id)
|
||||
{
|
||||
return ActiveModifiers.Exists(x => x.UniqueId == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier<T>(bool checkInactive, Func<T, bool>? predicate=null) where T : BaseModifier
|
||||
{
|
||||
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
|
||||
bool MatchExpr(BaseModifier bm) => bm is T modifier && (predicate == null || predicate(modifier));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier(Type type, bool checkInactive, Func<BaseModifier, bool>? predicate=null)
|
||||
{
|
||||
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
|
||||
bool MatchExpr(BaseModifier bm) => bm.GetType() == type && (predicate == null || predicate(bm));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier's type ID.</param>
|
||||
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
|
||||
/// <param name="predicate">The predicate to check the modifier.</param>
|
||||
/// <returns>True if the modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier(uint id, bool checkInactive, Func<BaseModifier, bool>? predicate=null)
|
||||
{
|
||||
var type = ModifierManager.GetModifierType(id) ?? throw new InvalidOperationException(
|
||||
$"Cannot get modifier with id {id} because it is not registered.");
|
||||
|
||||
return HasModifier(type, checkInactive, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has an active modifier by its unique ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The modifier's guid.</param>
|
||||
/// <param name="checkInactive">Whether to check inactive modifiers (those pending to be added).</param>
|
||||
/// <returns>True if the modifier is present, false otherwise.</returns>
|
||||
[HideFromIl2Cpp]
|
||||
public bool HasModifier(Guid id, bool checkInactive)
|
||||
{
|
||||
return ActiveModifiers.Exists(MatchExpr) || (checkInactive && _toAdd.Exists(MatchExpr));
|
||||
bool MatchExpr(BaseModifier bm) => bm.UniqueId == id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,472 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Networking.Modifiers;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for modifiers.
|
||||
/// </summary>
|
||||
public static class ModifierExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dictionary to cache ModifierComponents.
|
||||
/// </summary>
|
||||
public static Dictionary<PlayerControl, ModifierComponent> ModifierComponents { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to add a modifier to a player.
|
||||
/// </summary>
|
||||
/// <param name="target">The player to add the modifier to.</param>
|
||||
/// <param name="typeId">The modifier type ID.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
public static void RpcAddModifier(this PlayerControl target, uint typeId, params object[] args)
|
||||
{
|
||||
_ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
|
||||
$"Modifier with ID {typeId} is not registered.");
|
||||
|
||||
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to add a modifier to a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to add the modifier to.</param>
|
||||
/// <param name="type">The type of the modifier.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
public static void RpcAddModifier(this PlayerControl player, Type type, params object[] args)
|
||||
{
|
||||
var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException(
|
||||
$"Modifier {type.Name} is not registered.");
|
||||
|
||||
player.RpcAddModifier(id, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to add a modifier to a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to add the modifier to.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <typeparam name="T">The modifier Type.</typeparam>
|
||||
public static void RpcAddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
|
||||
{
|
||||
player.RpcAddModifier(typeof(T), args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to remove a modifier from a player.
|
||||
/// </summary>
|
||||
/// <param name="target">The player to remove the modifier from.</param>
|
||||
/// <param name="uniqueId">The unique ID of the modifier.</param>
|
||||
[MethodRpc((uint)MiraRpc.RemoveModifier)]
|
||||
public static void RpcRemoveModifier(this PlayerControl target, Guid uniqueId)
|
||||
{
|
||||
target.RemoveModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to remove a modifier from a player by type ID.
|
||||
/// </summary>
|
||||
/// <param name="target">The player to remove the modifier from.</param>
|
||||
/// <param name="typeId">The type ID of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
public static void RpcRemoveModifier(
|
||||
this PlayerControl target,
|
||||
uint typeId,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
var modifier = target.GetModifier(typeId, predicate);
|
||||
if (modifier is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
|
||||
return;
|
||||
}
|
||||
|
||||
target.RpcRemoveModifier(modifier.UniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to remove a modifier from a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to remove the modifier from.</param>
|
||||
/// <param name="type">The type of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
public static void RpcRemoveModifier(
|
||||
this PlayerControl player,
|
||||
Type type,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
var id = ModifierManager.GetModifierTypeId(type) ?? throw new InvalidOperationException(
|
||||
$"Modifier {type.Name} is not registered.");
|
||||
|
||||
player.RpcRemoveModifier(id, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to remove a modifier from a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to remove the modifier from.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
public static void RpcRemoveModifier<T>(this PlayerControl player, Func<BaseModifier, bool>? predicate = null)
|
||||
where T : BaseModifier
|
||||
{
|
||||
player.RpcRemoveModifier(typeof(T), predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ModifierComponent for a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl object.</param>
|
||||
/// <returns>A ModifierComponent if there is one, null otherwise.</returns>
|
||||
public static ModifierComponent GetModifierComponent(this PlayerControl player)
|
||||
{
|
||||
if (ModifierComponents.TryGetValue(player, out var component))
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
component = player.GetComponent<ModifierComponent>();
|
||||
if (!component)
|
||||
{
|
||||
throw new InvalidOperationException("ModifierComponent is not attached to the player.");
|
||||
}
|
||||
|
||||
ModifierComponents[player] = component;
|
||||
return component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has a specific modifier.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the modifier.</typeparam>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>True if the player has the modifier, false otherwise.</returns>
|
||||
public static bool HasModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return player.GetModifierComponent().HasModifier(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has a specific modifier by type.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="type">The type of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>True if the player has the modifier, false otherwise.</returns>
|
||||
public static bool HasModifier(this PlayerControl player, Type type, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().HasModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has a specific modifier by type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="typeId">The type ID of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>True if the player has the modifier, false otherwise.</returns>
|
||||
public static bool HasModifier(
|
||||
this PlayerControl player,
|
||||
uint typeId,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().HasModifier(typeId, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if the player has a specific modifier by its GUID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="uniqueId">The unique ID of the modifier.</param>
|
||||
/// <returns>True if the player has the modifier, false otherwise.</returns>
|
||||
public static bool HasModifier(this PlayerControl player, Guid uniqueId)
|
||||
{
|
||||
return player.GetModifierComponent().HasModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all modifiers from a player.
|
||||
/// </summary>
|
||||
/// <param name="plr">The player you want to clear modifiers for.</param>
|
||||
public static void ClearModifiers(this PlayerControl plr)
|
||||
{
|
||||
foreach (var mod in plr.GetModifierComponent().ActiveModifiers)
|
||||
{
|
||||
plr.RemoveModifier(mod);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears all modifiers from a player.
|
||||
/// </summary>
|
||||
/// <param name="plr">The player you want to clear modifiers for.</param>
|
||||
public static void RpcClearModifiers(this PlayerControl plr)
|
||||
{
|
||||
foreach (var mod in plr.GetModifierComponent().ActiveModifiers)
|
||||
{
|
||||
plr.RpcRemoveModifier(mod.UniqueId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
public static bool TryGetModifier<T>(this PlayerControl player, [NotNullWhen(true)] out T? modifier, Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return player.GetModifierComponent().TryGetModifier(out modifier, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its type.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
public static bool TryGetModifier(this PlayerControl player, Type type, [NotNullWhen(true)] out BaseModifier? modifier, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().TryGetModifier(type, out modifier, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="id">The modifier type ID.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <param name="predicate">The predicate to check the modifier by.</param>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
public static bool TryGetModifier(this PlayerControl player, uint id, [NotNullWhen(true)] out BaseModifier? modifier, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().TryGetModifier(id, out modifier, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get a modifier by its unique ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="modifierGuid">The modifier unique ID.</param>
|
||||
/// <param name="modifier">The modifier or null.</param>
|
||||
/// <returns>True if the modifier was found, false otherwise.</returns>
|
||||
public static bool TryGetModifier(this PlayerControl player, Guid modifierGuid, [NotNullWhen(true)] out BaseModifier? modifier)
|
||||
{
|
||||
return player.GetModifierComponent().TryGetModifier(modifierGuid, out modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific modifier from the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the modifier.</typeparam>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>The modifier if found, null otherwise.</returns>
|
||||
public static T? GetModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return player.GetModifierComponent().GetModifier(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific modifier from the player by type.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="type">The type of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>The modifier if found, null otherwise.</returns>
|
||||
public static BaseModifier? GetModifier(
|
||||
this PlayerControl player,
|
||||
Type type,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().GetModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific modifier from the player by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="typeId">The type ID of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>The modifier if found, null otherwise.</returns>
|
||||
public static BaseModifier? GetModifier(
|
||||
this PlayerControl player,
|
||||
uint typeId,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().GetModifier(typeId, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a specific modifier from the player by its GUID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="uniqueId">The GUID of the modifier.</param>
|
||||
/// <returns>The modifier if found, null otherwise.</returns>
|
||||
public static BaseModifier? GetModifier(
|
||||
this PlayerControl player,
|
||||
Guid uniqueId)
|
||||
{
|
||||
return player.GetModifierComponent().GetModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all modifiers of a specific type from the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the modifiers.</typeparam>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>A collection of modifiers.</returns>
|
||||
public static IEnumerable<T> GetModifiers<T>(this PlayerControl player, Func<T, bool>? predicate = null)
|
||||
where T : BaseModifier
|
||||
{
|
||||
return player.GetModifierComponent().GetModifiers(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all modifiers of a specific type from the player.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="type">The type of the modifiers.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>A collection of modifiers.</returns>
|
||||
public static IEnumerable<BaseModifier> GetModifiers(
|
||||
this PlayerControl player,
|
||||
Type type,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().GetModifiers(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all modifiers of a specific type from the player by type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="typeId">The type ID of the modifiers.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
/// <returns>A collection of modifiers.</returns>
|
||||
public static IEnumerable<BaseModifier> GetModifiers(
|
||||
this PlayerControl player,
|
||||
uint typeId,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
return player.GetModifierComponent().GetModifiers(typeId, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific modifier from the player.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="modifier">The modifier to remove.</param>
|
||||
public static void RemoveModifier(this PlayerControl player, BaseModifier modifier)
|
||||
{
|
||||
player.GetModifierComponent().RemoveModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific modifier from the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the modifier.</typeparam>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
public static void RemoveModifier<T>(this PlayerControl player, Func<T, bool>? predicate = null)
|
||||
where T : BaseModifier
|
||||
{
|
||||
player.GetModifierComponent().RemoveModifier(predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific modifier from the player by type.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="type">The type of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
public static void RemoveModifier(this PlayerControl player, Type type, Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
player.GetModifierComponent().RemoveModifier(type, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific modifier from the player by its type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="typeId">The type ID of the modifier.</param>
|
||||
/// <param name="predicate">Optional predicate to filter the modifiers.</param>
|
||||
public static void RemoveModifier(
|
||||
this PlayerControl player,
|
||||
uint typeId,
|
||||
Func<BaseModifier, bool>? predicate = null)
|
||||
{
|
||||
player.GetModifierComponent().RemoveModifier(typeId, predicate);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes a specific modifier from the player by its GUID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="uniqueId">The GUID of the modifier.</param>
|
||||
public static void RemoveModifier(
|
||||
this PlayerControl player,
|
||||
Guid uniqueId)
|
||||
{
|
||||
player.GetModifierComponent().RemoveModifier(uniqueId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a specific modifier to the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the modifier.</typeparam>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <returns>The added modifier.</returns>
|
||||
public static T? AddModifier<T>(this PlayerControl player, params object[] args) where T : BaseModifier
|
||||
{
|
||||
return player.GetModifierComponent().AddModifier<T>(args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a specific modifier to the player.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="modifier">The modifier to add.</param>
|
||||
/// <returns>The added modifier.</returns>
|
||||
public static BaseModifier? AddModifier(this PlayerControl player, BaseModifier modifier)
|
||||
{
|
||||
return player.GetModifierComponent().AddModifier(modifier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a specific modifier to the player by type.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="type">The type of the modifier.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <returns>The added modifier.</returns>
|
||||
public static BaseModifier? AddModifier(this PlayerControl player, Type type, params object[] args)
|
||||
{
|
||||
return player.GetModifierComponent().AddModifier(type, args);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a specific modifier to the player by type ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl instance.</param>
|
||||
/// <param name="typeId">The type ID of the modifier.</param>
|
||||
/// <param name="args">The arguments to initialize the modifier constructor with.</param>
|
||||
/// <returns>The added modifier.</returns>
|
||||
public static BaseModifier? AddModifier(this PlayerControl player, uint typeId, params object[] args)
|
||||
{
|
||||
return player.GetModifierComponent().AddModifier(typeId, args);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating instances of a modifier. More efficient than using reflection.
|
||||
/// </summary>
|
||||
public static class ModifierFactory
|
||||
{
|
||||
private static readonly Dictionary<(Type, Type[]), Func<object[], BaseModifier>> _constructorCache = [];
|
||||
|
||||
private static Func<object[], BaseModifier> CreateConstructor(Type type, params object[] args)
|
||||
{
|
||||
var constructorInfo = type.GetBestConstructor(args) ?? throw new InvalidOperationException(
|
||||
$"Could not find a constructor for type {type} with the specified arguments.");
|
||||
|
||||
var parameters = constructorInfo.GetParameters();
|
||||
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
var constructorParams = new Expression[parameters.Length];
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var paramType = parameters[i].ParameterType;
|
||||
var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
|
||||
var convertedParam = Expression.Convert(paramAccess, paramType);
|
||||
constructorParams[i] = convertedParam;
|
||||
}
|
||||
|
||||
var newExpr = Expression.New(constructorInfo, constructorParams);
|
||||
var lambda = Expression.Lambda<Func<object[], BaseModifier>>(newExpr, argsParam);
|
||||
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a modifier with the specified type and arguments.
|
||||
/// </summary>
|
||||
/// <param name="type">Modifier type.</param>
|
||||
/// <param name="args">Arguments.</param>
|
||||
/// <returns>An instance of the modifier.</returns>
|
||||
public static BaseModifier CreateInstance(Type type, params object[] args)
|
||||
{
|
||||
var argTypes = args.Select(arg => arg?.GetType() ?? typeof(object)).ToArray();
|
||||
var key = (type, argTypes);
|
||||
|
||||
if (!_constructorCache.TryGetValue(key, out var constructor))
|
||||
{
|
||||
constructor = CreateConstructor(type, args);
|
||||
_constructorCache[key] = constructor;
|
||||
}
|
||||
|
||||
return constructor(args);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Factory for creating instances of a modifier. More efficient than using reflection.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The modifier type.</typeparam>
|
||||
public static class ModifierFactory<T> where T : BaseModifier
|
||||
{
|
||||
private static readonly Func<object[], T> Constructor = CreateConstructor();
|
||||
|
||||
private static Func<object[], T> CreateConstructor(params object[] args)
|
||||
{
|
||||
var constructorInfo = typeof(T).GetBestConstructor(args) ?? throw new InvalidOperationException(
|
||||
$"Could not find a constructor for type {typeof(T)} with the specified arguments.");
|
||||
|
||||
var parameters = constructorInfo.GetParameters();
|
||||
|
||||
var argsParam = Expression.Parameter(typeof(object[]), "args");
|
||||
var constructorParams = new Expression[parameters.Length];
|
||||
|
||||
for (var i = 0; i < parameters.Length; i++)
|
||||
{
|
||||
var paramType = parameters[i].ParameterType;
|
||||
var paramAccess = Expression.ArrayIndex(argsParam, Expression.Constant(i));
|
||||
var convertedParam = Expression.Convert(paramAccess, paramType);
|
||||
constructorParams[i] = convertedParam;
|
||||
}
|
||||
|
||||
var newExpr = Expression.New(constructorInfo, constructorParams);
|
||||
var lambda = Expression.Lambda<Func<object[], T>>(newExpr, argsParam);
|
||||
|
||||
return lambda.Compile();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the modifier.
|
||||
/// </summary>
|
||||
/// <param name="args">Parameters for the constructor.</param>
|
||||
/// <returns>An instance of the modifier.</returns>
|
||||
public static T CreateInstance(params object[] args)
|
||||
{
|
||||
return Constructor(args);
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using Random = System.Random;
|
||||
@@ -21,16 +21,16 @@ public static class ModifierManager
|
||||
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
|
||||
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
|
||||
|
||||
private static uint _nextId;
|
||||
private static uint _nextTypeId;
|
||||
|
||||
private static uint GetNextId()
|
||||
private static uint GetNextTypeId()
|
||||
{
|
||||
_nextId++;
|
||||
return _nextId;
|
||||
_nextTypeId++;
|
||||
return _nextTypeId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier type from the id.
|
||||
/// Gets the modifier type from the type id.
|
||||
/// </summary>
|
||||
/// <param name="id">The ID.</param>
|
||||
/// <returns>The Type of the modifier.</returns>
|
||||
@@ -44,35 +44,58 @@ public static class ModifierManager
|
||||
/// </summary>
|
||||
/// <param name="type">The Type.</param>
|
||||
/// <returns>The ID of the modifier.</returns>
|
||||
public static uint? GetModifierId(Type type)
|
||||
public static uint? GetModifierTypeId(Type type)
|
||||
{
|
||||
return TypeToIdModifierMap.GetValueOrDefault(type);
|
||||
if (!TypeToIdModifierMap.TryGetValue(type, out var id))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
internal static bool RegisterModifier(Type modifierType)
|
||||
internal static bool RegisterModifier(Type modifierType, MiraPluginInfo info)
|
||||
{
|
||||
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
IdToTypeModifierMap.Add(GetNextId(), modifierType);
|
||||
TypeToIdModifierMap.Add(modifierType, _nextId);
|
||||
IdToTypeModifierMap.Add(GetNextTypeId(), modifierType);
|
||||
TypeToIdModifierMap.Add(modifierType, _nextTypeId);
|
||||
|
||||
if (!typeof(GameModifier).IsAssignableFrom(modifierType))
|
||||
BaseModifier modifier;
|
||||
if (modifierType.GetConstructor(Type.EmptyTypes) != null)
|
||||
{
|
||||
modifier = (BaseModifier)Activator.CreateInstance(modifierType)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
// this is probably terrible but its good enough for now.
|
||||
modifier = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType);
|
||||
}
|
||||
|
||||
info.Modifiers.Add(modifier);
|
||||
|
||||
if (modifier is not GameModifier gameModifier)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var mod = Activator.CreateInstance(modifierType) as GameModifier;
|
||||
var priority = mod!.Priority();
|
||||
if (modifierType.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Game Modifier {modifierType.FullName} does not have a parameterless constructor!");
|
||||
return false;
|
||||
}
|
||||
|
||||
var priority = gameModifier.Priority();
|
||||
|
||||
if (!PrioritiesToIdsMap.TryGetValue(priority, out var list))
|
||||
{
|
||||
PrioritiesToIdsMap[priority] = list = [];
|
||||
}
|
||||
|
||||
list.Add(_nextId);
|
||||
list.Add(_nextTypeId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -80,68 +103,57 @@ public static class ModifierManager
|
||||
{
|
||||
var rand = new Random();
|
||||
|
||||
foreach (var priority in PrioritiesToIdsMap.Keys.OrderByDescending(x => x))
|
||||
// Filter and sort modifiers by descending priority.
|
||||
var modifiers = IdToTypeModifierMap
|
||||
.Where(x=>x.Value.IsAssignableTo(typeof(GameModifier)))
|
||||
.Select(x => Activator.CreateInstance(x.Value) as GameModifier)
|
||||
.OfType<GameModifier>()
|
||||
.Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0)
|
||||
.OrderByDescending(x => x.Priority())
|
||||
.ToArray();
|
||||
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
var filteredModifiers = new List<uint>();
|
||||
var assignments = modifier.GetAmountPerGame();
|
||||
|
||||
foreach (var id in PrioritiesToIdsMap[priority])
|
||||
var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList();
|
||||
if (validPlayers.Count == 0)
|
||||
{
|
||||
var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
|
||||
var chance = Math.Clamp(mod!.GetAssignmentChance(), 0, 100);
|
||||
var count = mod.GetAmountPerGame();
|
||||
|
||||
if (chance == 0 || count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var maxCount = plrs.Count(x => IsGameModifierValid(x, mod, id));
|
||||
|
||||
if (maxCount == 0)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"No players are valid for {mod.ModifierName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var num = Math.Clamp(count, 0, maxCount);
|
||||
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
var randomNum = rand.Next(100);
|
||||
|
||||
if (randomNum < chance)
|
||||
{
|
||||
filteredModifiers.Add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (filteredModifiers.Count == 0)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"No filtered modifiers for priority {priority}");
|
||||
Logger<MiraApiPlugin>.Warning($"No valid players for modifier {modifier.ModifierName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var shuffledList = filteredModifiers.Randomize();
|
||||
assignments = Math.Min(assignments, validPlayers.Count);
|
||||
var availablePlayers = new List<PlayerControl>(validPlayers);
|
||||
|
||||
if (shuffledList.Count > plrs.Count)
|
||||
for (var i = 0; i < assignments; i++)
|
||||
{
|
||||
shuffledList = shuffledList.GetRange(0, plrs.Count);
|
||||
}
|
||||
var chance = Math.Clamp(modifier.GetAssignmentChance(), 0, 100);
|
||||
if (rand.Next(100) >= chance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var id in shuffledList)
|
||||
{
|
||||
var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
|
||||
var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random();
|
||||
var candidates = availablePlayers
|
||||
.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId))
|
||||
.ToList();
|
||||
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning(
|
||||
$"No available players for modifier {modifier.ModifierName} at assignment {i + 1}");
|
||||
break;
|
||||
}
|
||||
|
||||
var plr = candidates.Random();
|
||||
if (plr == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared");
|
||||
}
|
||||
else
|
||||
{
|
||||
plr.RpcAddModifier(id);
|
||||
Logger<MiraApiPlugin>.Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
|
||||
continue;
|
||||
}
|
||||
|
||||
plr.RpcAddModifier(modifier.TypeId);
|
||||
availablePlayers.Remove(plr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,71 +161,6 @@ public static class ModifierManager
|
||||
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
|
||||
{
|
||||
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
|
||||
modifier.IsModifierValidOn(player.Data.Role) &&
|
||||
!player.HasModifier(modifierId);
|
||||
}
|
||||
|
||||
internal static void SyncAllModifiers(int targetId = -1)
|
||||
{
|
||||
var data = new List<NetData>();
|
||||
|
||||
foreach (var player in PlayerControl.AllPlayerControls)
|
||||
{
|
||||
data.Add(GetPlayerModifiers(player));
|
||||
}
|
||||
|
||||
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]);
|
||||
}
|
||||
|
||||
internal static void HandleSyncModifiers(NetData[] data)
|
||||
{
|
||||
foreach (var netData in data)
|
||||
{
|
||||
var ids = new uint[netData.Data.Length / 4];
|
||||
Buffer.BlockCopy(netData.Data, 0, ids, 0, netData.Data.Length);
|
||||
|
||||
var plr = GameData.Instance.GetPlayerById((byte)netData.Id).Object;
|
||||
var modifierComponent = plr.GetComponent<ModifierComponent>();
|
||||
|
||||
if (!modifierComponent)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
modifierComponent.ClearModifiers();
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
if (!IdToTypeModifierMap.TryGetValue(id, out var type))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {id} because it is not registered.");
|
||||
continue;
|
||||
}
|
||||
|
||||
modifierComponent.AddModifier(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static NetData GetPlayerModifiers(PlayerControl? player)
|
||||
{
|
||||
if (player == null || !player)
|
||||
{
|
||||
return new NetData(0, []);
|
||||
}
|
||||
|
||||
List<byte> bytes = [];
|
||||
var modifierComponent = player.GetComponent<ModifierComponent>();
|
||||
if (modifierComponent == null || !modifierComponent)
|
||||
{
|
||||
return new NetData(player.PlayerId, []);
|
||||
}
|
||||
|
||||
foreach (var modifier in modifierComponent.ActiveModifiers)
|
||||
{
|
||||
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifierMap[modifier.GetType()]));
|
||||
}
|
||||
|
||||
return new NetData(player.PlayerId, [.. bytes]);
|
||||
modifier.IsModifierValidOn(player.Data.Role) && !player.HasModifier(modifierId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Utilities to make handling modifiers in-game easier.
|
||||
/// </summary>
|
||||
public static class ModifierUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all active in-game modifiers.
|
||||
/// </summary>
|
||||
/// <param name="predicate">Select if modifier is valid to be added to list.</param>
|
||||
/// <typeparam name="T">The modifier type.</typeparam>
|
||||
/// <returns>A list of modifiers.</returns>
|
||||
public static IEnumerable<T> GetActiveModifiers<T>(Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return PlayerControl.AllPlayerControls.ToArray().SelectMany(x => x.GetModifiers<T>(predicate)).OfType<T>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all players with a certain modifier.
|
||||
/// </summary>
|
||||
/// <param name="predicate">Select if modifier is valid.</param>
|
||||
/// <typeparam name="T">The modifier type.</typeparam>
|
||||
/// <returns>A list of players with that modifier.</returns>
|
||||
public static IEnumerable<PlayerControl> GetPlayersWithModifier<T>(Func<T, bool>? predicate = null) where T : BaseModifier
|
||||
{
|
||||
return PlayerControl.AllPlayerControls.ToArray().Where(x => x.HasModifier<T>(predicate));
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,9 @@
|
||||
/// </summary>
|
||||
public abstract class GameModifier : BaseModifier
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override bool ShowInFreeplay => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the chance of the modifier being assigned to a player.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,7 +17,7 @@ public abstract class TimedModifier : BaseModifier
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the timer should start automatically when added.
|
||||
/// </summary>
|
||||
public virtual bool AutoStart => false;
|
||||
public virtual bool AutoStart => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier should be removed when the timer completes.
|
||||
@@ -27,7 +27,9 @@ public abstract class TimedModifier : BaseModifier
|
||||
/// <summary>
|
||||
/// Called when the timer completes.
|
||||
/// </summary>
|
||||
public abstract void OnTimerComplete();
|
||||
public virtual void OnTimerComplete()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the timer is active.
|
||||
@@ -102,7 +104,7 @@ public abstract class TimedModifier : BaseModifier
|
||||
|
||||
if (RemoveOnComplete)
|
||||
{
|
||||
ModifierComponent?.RemoveModifier(ModifierId);
|
||||
ModifierComponent?.RemoveModifier(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Collections.Generic;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
using MiraAPI.GameEnd;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
|
||||
/// <summary>
|
||||
/// Custom Game Over RPC.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
[RegisterCustomRpc((uint)MiraRpc.CustomGameOver)]
|
||||
public class CustomGameOverRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, GameOverData>(plugin, id)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.After;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(MessageWriter writer, GameOverData data)
|
||||
{
|
||||
writer.WritePacked(data.Reason);
|
||||
writer.WritePacked(data.Winners.Count);
|
||||
foreach (var player in data.Winners)
|
||||
{
|
||||
writer.WriteNetObject(player);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override GameOverData Read(MessageReader reader)
|
||||
{
|
||||
var reason = reader.ReadPackedInt32();
|
||||
var count = reader.ReadPackedInt32();
|
||||
var data = new List<NetworkedPlayerInfo>(count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
data.Add(reader.ReadNetObject<NetworkedPlayerInfo>());
|
||||
}
|
||||
|
||||
return new GameOverData(reason, data);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Handle(PlayerControl innerNetObject, GameOverData data)
|
||||
{
|
||||
if (GameOverManager.TryGetGameOver(data.Reason, out var gameOver))
|
||||
{
|
||||
if (!gameOver.VerifyCondition(innerNetObject, [.. data.Winners]))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"Game over condition not met for {gameOver.GetType().Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
CustomGameOver.Instance = gameOver;
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
GameManager.Instance.RpcEndGame(gameOver, false);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CustomGameOver.Instance = null;
|
||||
Logger<MiraApiPlugin>.Error($"Unknown game over reason: {data.Reason}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using AmongUs.Data;
|
||||
using AmongUs.GameOptions;
|
||||
using Assets.CoreScripts;
|
||||
using BepInEx.Unity.IL2CPP.Utils;
|
||||
using MiraAPI.Events;
|
||||
@@ -6,8 +9,6 @@ using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
@@ -96,7 +97,7 @@ public static class CustomMurderRpc
|
||||
var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
|
||||
if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId)
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected);
|
||||
DataManager.Player.Stats.IncrementStat(StatID.Role_GuardianAngel_CrewmatesProtected);
|
||||
AchievementManager.Instance.OnProtectACrewmate();
|
||||
}
|
||||
|
||||
@@ -127,14 +128,14 @@ public static class CustomMurderRpc
|
||||
DebugAnalytics.Instance.Analytics.Kill(target.Data, source.Data);
|
||||
if (source.AmOwner)
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(
|
||||
DataManager.Player.Stats.IncrementStat(
|
||||
GameManager.Instance.IsHideAndSeek()
|
||||
? StringNames.StatsImpostorKills_HideAndSeek
|
||||
: StringNames.StatsImpostorKills);
|
||||
? StatID.HideAndSeek_ImpostorKills
|
||||
: StatID.ImpostorKills);
|
||||
|
||||
if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted)
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills);
|
||||
DataManager.Player.Stats.IncrementStat(StatID.Role_Shapeshifter_ShiftedKills);
|
||||
}
|
||||
|
||||
if (Constants.ShouldPlaySfx() && playKillSound)
|
||||
@@ -152,7 +153,7 @@ public static class CustomMurderRpc
|
||||
target.gameObject.layer = LayerMask.NameToLayer("Ghost");
|
||||
if (target.AmOwner)
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered);
|
||||
DataManager.Player.Stats.IncrementStat(StatID.TimesMurdered);
|
||||
if (Minigame.Instance)
|
||||
{
|
||||
try
|
||||
@@ -263,6 +264,9 @@ public static class CustomMurderRpc
|
||||
deadBody.enabled = true;
|
||||
}
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target);
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
|
||||
if (!isParticipant)
|
||||
{
|
||||
yield break;
|
||||
@@ -275,8 +279,5 @@ public static class CustomMurderRpc
|
||||
|
||||
PlayerControl.LocalPlayer.isKilling = false;
|
||||
source.isKilling = false;
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target);
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using Hazel;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Serialization;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for <see cref="Guid"/>.
|
||||
/// </summary>
|
||||
[MessageConverter]
|
||||
public class GuidConverter : MessageConverter<Guid>
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a <see cref="Guid"/> to the writer.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public override void Write(MessageWriter writer, Guid value)
|
||||
{
|
||||
writer.WriteBytesAndSize(value.ToByteArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="Guid"/> from the reader.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
/// <returns>A GUID object.</returns>
|
||||
public override Guid Read(MessageReader reader, Type objectType)
|
||||
{
|
||||
return new Guid(reader.ReadBytesAndSize());
|
||||
}
|
||||
}
|
||||
@@ -34,4 +34,14 @@ public enum MiraRpc : uint
|
||||
/// Custom Murder RPC.
|
||||
/// </summary>
|
||||
CustomMurder,
|
||||
|
||||
/// <summary>
|
||||
/// Custom Game Over RPC.
|
||||
/// </summary>
|
||||
CustomGameOver,
|
||||
|
||||
/// <summary>
|
||||
/// Custom RPC to undo a player's vote.
|
||||
/// </summary>
|
||||
RemoveVote,
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
using System;
|
||||
using Hazel;
|
||||
using MiraAPI.Modifiers;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Networking.Serialization;
|
||||
|
||||
namespace MiraAPI.Networking.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Remote procedure call for adding a modifier.
|
||||
/// </summary>
|
||||
/// <param name="plugin">Mira plugin.</param>
|
||||
/// <param name="id">RPC ID.</param>
|
||||
[RegisterCustomRpc((uint)MiraRpc.AddModifier)]
|
||||
public class AddModifierRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, ModifierData>(plugin, id)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.Before;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(MessageWriter writer, ModifierData data)
|
||||
{
|
||||
writer.WritePacked(data.TypeId);
|
||||
writer.WriteBytesAndSize(data.UniqueId.ToByteArray());
|
||||
writer.WritePacked(data.Args.Length);
|
||||
foreach (var arg in data.Args)
|
||||
{
|
||||
writer.Write(arg.GetType().AssemblyQualifiedName);
|
||||
writer.Serialize(arg);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ModifierData Read(MessageReader reader)
|
||||
{
|
||||
var modId = reader.ReadPackedUInt32();
|
||||
var guid = new Guid(reader.ReadBytesAndSize());
|
||||
var argCount = reader.ReadPackedUInt32();
|
||||
var objects = new object[argCount];
|
||||
|
||||
if (argCount > 0)
|
||||
{
|
||||
var types = new Type[argCount];
|
||||
for (var i = 0; i < argCount; i++)
|
||||
{
|
||||
var name = reader.ReadString();
|
||||
types[i] = Type.GetType(name) ?? throw new InvalidOperationException($"Type not found: {name}");
|
||||
objects[i] = reader.Deserialize(types[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return new ModifierData(modId, guid, objects);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Handle(PlayerControl player, ModifierData data)
|
||||
{
|
||||
var type = ModifierManager.GetModifierType(data.TypeId) ?? throw new InvalidOperationException($"Modifier type not found for ID {data.TypeId}.");
|
||||
BaseModifier? modifier;
|
||||
if (data.Args.Length > 0)
|
||||
{
|
||||
modifier = ModifierFactory.CreateInstance(type, data.Args);
|
||||
}
|
||||
else
|
||||
{
|
||||
modifier = Activator.CreateInstance(type) as BaseModifier;
|
||||
if (modifier == null)
|
||||
{
|
||||
throw new InvalidOperationException($"Cannot add modifier {type.Name} because it is not a valid modifier.");
|
||||
}
|
||||
}
|
||||
modifier.UniqueId = data.UniqueId;
|
||||
player.AddModifier(modifier);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using Hazel;
|
||||
using InnerNet;
|
||||
using MiraAPI.Modifiers;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Serialization;
|
||||
|
||||
namespace MiraAPI.Networking.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Converter for serializing and deserializing <see cref="BaseModifier"/> objects.
|
||||
/// </summary>
|
||||
[MessageConverter]
|
||||
public class ModifierConverter : MessageConverter<BaseModifier>
|
||||
{
|
||||
/// <summary>
|
||||
/// Writes a <see cref="BaseModifier"/> to the writer.
|
||||
/// </summary>
|
||||
/// <param name="writer">The writer to write to.</param>
|
||||
/// <param name="value">The <see cref="BaseModifier"/> to write.</param>
|
||||
public override void Write(MessageWriter writer, BaseModifier value)
|
||||
{
|
||||
writer.WriteNetObject(value.Player);
|
||||
writer.WriteBytesAndSize(value.UniqueId.ToByteArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a <see cref="BaseModifier"/> from the reader.
|
||||
/// </summary>
|
||||
/// <param name="reader">The reader to read from.</param>
|
||||
/// <param name="objectType">The type of the object to read.</param>
|
||||
/// <returns>The <see cref="BaseModifier"/> that was read.</returns>
|
||||
/// <exception cref="InvalidOperationException">Thrown if the modifier is not found.</exception>
|
||||
public override BaseModifier Read(MessageReader reader, Type objectType)
|
||||
{
|
||||
var player = reader.ReadNetObject<PlayerControl>();
|
||||
var guid = new Guid(reader.ReadBytesAndSize());
|
||||
return player.GetModifier(guid) ?? throw new InvalidOperationException($"Modifier with GUID {guid} not found for player with ID: {player.PlayerId}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Networking.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Modifier data for networking.
|
||||
/// </summary>
|
||||
/// <param name="TypeId">The ID of the modifier.</param>
|
||||
/// <param name="UniqueId">The unique ID of the modifier.</param>
|
||||
/// <param name="Args">Parameters for constructor.</param>
|
||||
public record struct ModifierData(uint TypeId, Guid UniqueId, object[] Args);
|
||||
@@ -1,52 +0,0 @@
|
||||
using Hazel;
|
||||
using MiraAPI.Modifiers;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
|
||||
[RegisterCustomRpc((uint)MiraRpc.SyncModifiers)]
|
||||
internal sealed class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
|
||||
{
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public override void Write(MessageWriter writer, NetData[]? data)
|
||||
{
|
||||
if (data == null)
|
||||
{
|
||||
writer.WritePacked(0U);
|
||||
return;
|
||||
}
|
||||
|
||||
writer.WritePacked((uint)data.Length);
|
||||
foreach (var netData in data)
|
||||
{
|
||||
writer.WritePacked(netData.Id);
|
||||
writer.WriteBytesAndSize(netData.Data);
|
||||
}
|
||||
}
|
||||
|
||||
public override NetData[] Read(MessageReader reader)
|
||||
{
|
||||
var length = reader.ReadPackedUInt32();
|
||||
var data = new NetData[length];
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
var dataId = reader.ReadPackedUInt32();
|
||||
var bytes = reader.ReadBytesAndSize();
|
||||
data[i] = new NetData(dataId, bytes);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl playerControl, NetData[]? data)
|
||||
{
|
||||
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierManager.HandleSyncModifiers(data ?? []);
|
||||
}
|
||||
}
|
||||
@@ -3,16 +3,15 @@ using InnerNet;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// Sync all options, role settings, and modifiers to the player when they join the game.
|
||||
/// Sync all settings to the player when they join the game.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(AmongUsClient))]
|
||||
public static class AmongUsClientSyncPatch
|
||||
internal static class AmongUsClientSyncPatch
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))]
|
||||
@@ -33,7 +32,6 @@ public static class AmongUsClientSyncPatch
|
||||
|
||||
ModdedOptionsManager.SyncAllOptions(clientData.Id);
|
||||
CustomRoleManager.SyncAllRoleSettings(clientData.Id);
|
||||
ModifierManager.SyncAllModifiers(clientData.Id);
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
|
||||
@@ -4,17 +4,19 @@ using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Colors;
|
||||
|
||||
[HarmonyPatch(typeof(PlayerTab))]
|
||||
/// <summary>
|
||||
/// This patch adds scrolling to the colors tab in the player customization menu.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(PlayerTab), nameof(PlayerTab.OnEnable))]
|
||||
public static class ScrollingColorsPatch
|
||||
{
|
||||
// Collider
|
||||
private static BoxCollider2D? _collider;
|
||||
|
||||
/// <summary>
|
||||
/// Add scrolling to the colors tab
|
||||
/// Add scrolling to the colors tab.
|
||||
/// </summary>
|
||||
[HarmonyPostfix, HarmonyPatch("OnEnable")]
|
||||
public static void AddScrollingToColorsTabPatch(PlayerTab __instance)
|
||||
public static void Postfix(PlayerTab __instance)
|
||||
{
|
||||
if (!PlayerCustomizationMenu.Instance)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using HarmonyLib;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Usables;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.GameEnd;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Patch for trigging the GameEndEvent.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class EndGamePatches
|
||||
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
|
||||
internal static class EndGamePatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
|
||||
public static void SetEverythingUpPatch(EndGameManager __instance)
|
||||
public static bool Prefix(EndGameManager __instance)
|
||||
{
|
||||
if (CustomGameOver.Instance is not { } gameOver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var result = gameOver.BeforeEndGameSetup(__instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void Postfix(EndGameManager __instance)
|
||||
{
|
||||
if (CustomGameOver.Instance is { } gameOver)
|
||||
{
|
||||
gameOver.AfterEndGameSetup(__instance);
|
||||
CustomGameOver.Instance = null;
|
||||
}
|
||||
|
||||
var @event = new GameEndEvent(__instance);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
/// <summary>
|
||||
/// General patches for the KillAnimation class.
|
||||
|
||||
+2
-2
@@ -6,7 +6,7 @@ using MiraAPI.Utilities;
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class StartMeetingEventPatch
|
||||
public static class ReportBodyEventPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
|
||||
@@ -14,7 +14,7 @@ public static class StartMeetingEventPatch
|
||||
{
|
||||
var body = target != null ? Helpers.GetBodyById(target.PlayerId) : null;
|
||||
|
||||
var @event = new StartMeetingEvent(__instance, target, body);
|
||||
var @event = new ReportBodyEvent(__instance, target, body);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
return !@event.IsCancelled;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch(typeof(SpawnInMinigame), nameof(SpawnInMinigame.Close))]
|
||||
internal static class SpawnInMinigamePatch
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
var @event = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch]
|
||||
internal static class SubmergedSpawnGamePatch
|
||||
{
|
||||
public static bool Prepare()
|
||||
{
|
||||
return ModCompatibility.SubmergedLoaded(out _);
|
||||
}
|
||||
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
if (ModCompatibility.SubmergedLoaded(out var subAss) &&
|
||||
subAss.GetType("Submerged.SpawnIn.SubmarineSelectSpawn") is { } selectSpawn)
|
||||
{
|
||||
yield return AccessTools.Method(selectSpawn, "OnDestroy");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Postfix()
|
||||
{
|
||||
var @event = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Usables;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Usables;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Freeplay;
|
||||
|
||||
[HarmonyPatch(typeof(TaskAddButton))]
|
||||
internal static class TaskAddButtonPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(TaskAddButton.Start))]
|
||||
public static bool StartPrefix(TaskAddButton __instance)
|
||||
{
|
||||
// if this becomes problematic in the future, find a new method.
|
||||
if (uint.TryParse(__instance.name, out var result))
|
||||
{
|
||||
__instance.Overlay.sprite = __instance.CheckImage;
|
||||
__instance.Overlay.enabled = PlayerControl.LocalPlayer.HasModifier(result);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(TaskAddButton.Role), MethodType.Setter)]
|
||||
public static void RoleGetterPatch(TaskAddButton __instance)
|
||||
{
|
||||
if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
|
||||
{
|
||||
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
|
||||
}
|
||||
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
|
||||
}
|
||||
}
|
||||
+143
-76
@@ -1,32 +1,27 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
namespace MiraAPI.Patches.Freeplay;
|
||||
|
||||
/// <summary>
|
||||
/// Patch TaskAdder for organizing Roles and adding scroller.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class TaskAdderPatch
|
||||
[HarmonyPatch(typeof(TaskAdderGame))]
|
||||
internal static class TaskAdderPatches
|
||||
{
|
||||
private static readonly Dictionary<string, string> ModsFolders = [];
|
||||
private static Scroller? _scroller;
|
||||
private static Scroller _scroller = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Adds custom roles to the TaskAdder.
|
||||
/// </summary>
|
||||
/// <param name="__instance">TaskAdder instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))]
|
||||
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
|
||||
public static void AddRolesFolder(TaskAdderGame __instance)
|
||||
{
|
||||
GameObject inner = new("Inner");
|
||||
@@ -67,45 +62,38 @@ public static class TaskAdderPatch
|
||||
rolesFolder.FolderName = "Roles";
|
||||
rolesFolder.name = "RolesFolder";
|
||||
|
||||
var modifiersFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
modifiersFolder.gameObject.SetActive(false);
|
||||
modifiersFolder.FolderName = "Modifiers";
|
||||
modifiersFolder.name = "ModifiersFolder";
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
{
|
||||
if (plugin.CustomRoles.Count == 0)
|
||||
if (plugin.CustomRoles.Any(x => x.Value is ICustomRole { Configuration.ShowInFreeplay: true }))
|
||||
{
|
||||
continue;
|
||||
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
newFolder.name = plugin.PluginId+":Roles";
|
||||
newFolder.FolderName = plugin.PluginInfo.Metadata.Name;
|
||||
newFolder.gameObject.SetActive(false);
|
||||
rolesFolder.SubFolders.Add(newFolder);
|
||||
}
|
||||
|
||||
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
|
||||
newFolder.gameObject.SetActive(false);
|
||||
rolesFolder.SubFolders.Add(newFolder);
|
||||
|
||||
if (!ModsFolders.ContainsKey(plugin.PluginInfo.Metadata.Name))
|
||||
if (plugin.Modifiers.Exists(x => x.ShowInFreeplay))
|
||||
{
|
||||
ModsFolders.Add(plugin.PluginInfo.Metadata.Name, plugin.PluginId);
|
||||
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
newFolder.name = plugin.PluginId+":Modifiers";
|
||||
newFolder.FolderName = plugin.PluginInfo.Metadata.Name;
|
||||
newFolder.gameObject.SetActive(false);
|
||||
modifiersFolder.SubFolders.Add(newFolder);
|
||||
}
|
||||
}
|
||||
|
||||
__instance.Root.SubFolders.Add(rolesFolder);
|
||||
__instance.Root.SubFolders.Add(modifiersFolder);
|
||||
|
||||
__instance.GoToRoot();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color of the role button based on the role.
|
||||
/// </summary>
|
||||
/// <param name="__instance">TaskAddButton instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(TaskAddButton), nameof(TaskAddButton.Role), MethodType.Setter)]
|
||||
public static void RoleGetterPatch(TaskAddButton __instance)
|
||||
{
|
||||
if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
|
||||
{
|
||||
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
|
||||
}
|
||||
|
||||
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
|
||||
}
|
||||
|
||||
private static void AddFileAsChildCustom(
|
||||
this TaskAdderGame instance,
|
||||
TaskAddButton item,
|
||||
@@ -130,8 +118,8 @@ public static class TaskAdderPatch
|
||||
|
||||
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
|
||||
public static bool ShowPatch(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder)
|
||||
[HarmonyPatch(nameof(TaskAdderGame.ShowFolder))]
|
||||
public static bool ShowPatch(TaskAdderGame __instance, TaskFolder taskFolder)
|
||||
{
|
||||
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
|
||||
__instance.Hierarchy.Add(taskFolder);
|
||||
@@ -277,52 +265,37 @@ public static class TaskAdderPatch
|
||||
}
|
||||
}
|
||||
|
||||
if (ModsFolders.TryGetValue(taskFolder.FolderName, out var guid))
|
||||
if (taskFolder)
|
||||
{
|
||||
var plugin = MiraPluginManager.GetPluginByGuid(guid);
|
||||
for (var m = 0; m < plugin.CustomRoles.Count; m++)
|
||||
var split = taskFolder.name.Split(':');
|
||||
if (split.Length == 2)
|
||||
{
|
||||
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
|
||||
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
|
||||
(roleBehaviour is ICustomRole customRole && !customRole.Configuration.ShowInFreeplay))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var pluginId = split[0];
|
||||
var folderName = split[1];
|
||||
|
||||
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
|
||||
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
|
||||
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
|
||||
__instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
|
||||
taskAddButton2.Role = roleBehaviour;
|
||||
if (taskAddButton2.Button == null)
|
||||
if (MiraPluginManager.GetPluginByGuid(pluginId) is { } plugin)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
|
||||
switch (m)
|
||||
{
|
||||
case 0 when __instance.restorePreviousSelectionFound != null:
|
||||
ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound);
|
||||
__instance.restorePreviousSelectionByFolderName = string.Empty;
|
||||
__instance.restorePreviousSelectionFound = null;
|
||||
break;
|
||||
case 0:
|
||||
ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
|
||||
break;
|
||||
if (folderName == "Roles(Clone)")
|
||||
{
|
||||
PluginRoles(__instance, plugin, ref num, ref num2, ref num3);
|
||||
}
|
||||
else if (folderName == "Modifiers(Clone)")
|
||||
{
|
||||
PluginModifiers(__instance, plugin, ref num, ref num2, ref num3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var chip in __instance.ActiveItems)
|
||||
{
|
||||
chip.GetComponentInChildren<TextMeshPro>().EnableMasking();
|
||||
chip.GetComponentInChildren<TextMeshPro>().EnableStencilMasking();
|
||||
chip.GetComponent<SpriteRenderer>().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||
}
|
||||
|
||||
if (_scroller && _scroller != null)
|
||||
if (_scroller)
|
||||
{
|
||||
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 3, 1f);
|
||||
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 5, 1.4f);
|
||||
_scroller.SetYBoundsMin(0.0f);
|
||||
_scroller.ScrollToTop();
|
||||
}
|
||||
@@ -336,4 +309,98 @@ public static class TaskAdderPatch
|
||||
ControllerManager.Instance.SetBackButton(__instance.FolderBackButton);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void PluginModifiers(
|
||||
TaskAdderGame instance,
|
||||
MiraPluginInfo plugin,
|
||||
ref float num,
|
||||
ref float num2,
|
||||
ref float num3)
|
||||
{
|
||||
for (var m = 0; m < plugin.Modifiers.Count; m++)
|
||||
{
|
||||
var modifier = plugin.Modifiers[m];
|
||||
if (!modifier.ShowInFreeplay)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var taskAddButton = Object.Instantiate(instance.RoleButton);
|
||||
taskAddButton.name = modifier.TypeId.ToString(NumberFormatInfo.InvariantInfo);
|
||||
taskAddButton.role = null;
|
||||
taskAddButton.MyTask = null;
|
||||
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
|
||||
taskAddButton.Text.text = modifier.ModifierName;
|
||||
taskAddButton.Text.EnableMasking();
|
||||
|
||||
taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
|
||||
taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
|
||||
{
|
||||
var id = modifier.TypeId;
|
||||
if (PlayerControl.LocalPlayer.HasModifier(id))
|
||||
{
|
||||
PlayerControl.LocalPlayer.RemoveModifier(id);
|
||||
taskAddButton.Overlay.enabled = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerControl.LocalPlayer.AddModifier(id);
|
||||
taskAddButton.Overlay.enabled = true;
|
||||
}
|
||||
}));
|
||||
|
||||
instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
|
||||
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
|
||||
switch (m)
|
||||
{
|
||||
case 0 when instance.restorePreviousSelectionFound != null:
|
||||
ControllerManager.Instance.SetDefaultSelection(instance.restorePreviousSelectionFound);
|
||||
instance.restorePreviousSelectionByFolderName = string.Empty;
|
||||
instance.restorePreviousSelectionFound = null;
|
||||
break;
|
||||
case 0:
|
||||
ControllerManager.Instance.SetDefaultSelection(taskAddButton.Button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void PluginRoles(TaskAdderGame instance, MiraPluginInfo plugin, ref float num, ref float num2, ref float num3)
|
||||
{
|
||||
for (var m = 0; m < plugin.CustomRoles.Count; m++)
|
||||
{
|
||||
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
|
||||
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
|
||||
roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var taskAddButton2 = Object.Instantiate(instance.RoleButton);
|
||||
taskAddButton2.SafePositionWorld = instance.SafePositionWorld;
|
||||
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
|
||||
taskAddButton2.Text.EnableMasking();
|
||||
|
||||
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
|
||||
taskAddButton2.Role = roleBehaviour;
|
||||
if (taskAddButton2.Button == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
|
||||
switch (m)
|
||||
{
|
||||
case 0 when instance.restorePreviousSelectionFound != null:
|
||||
ControllerManager.Instance.SetDefaultSelection(instance.restorePreviousSelectionFound);
|
||||
instance.restorePreviousSelectionByFolderName = string.Empty;
|
||||
instance.restorePreviousSelectionFound = null;
|
||||
break;
|
||||
case 0:
|
||||
ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Hud;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Hud;
|
||||
|
||||
@@ -26,7 +27,8 @@ public static class ButtonResetPatches
|
||||
/// Resets the cooldown and effect of all custom buttons after the exile screen is closed.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))]
|
||||
[HarmonyPatch(typeof(AirshipExileController), nameof(AirshipExileController.WrapUpAndSpawn))]
|
||||
public static void ExileControllerReEnableGameplayPostfix()
|
||||
{
|
||||
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
||||
|
||||
@@ -3,7 +3,6 @@ using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
@@ -32,7 +31,16 @@ public static class IntroCutscenePatches
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(IntroCutscene.BeginImpostor))]
|
||||
public static void BeginImpostorPatch(IntroCutscene __instance)
|
||||
[HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))]
|
||||
public static bool BeginPrefix(IntroCutscene __instance, [HarmonyArgument(0)] ref Il2CppSystem.Collections.Generic.List<PlayerControl> yourTeam)
|
||||
{
|
||||
return PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole || customRole.SetupIntroTeam(__instance, ref yourTeam);
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(IntroCutscene.BeginImpostor))]
|
||||
[HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))]
|
||||
public static void BeginPostfix(IntroCutscene __instance)
|
||||
{
|
||||
if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole)
|
||||
{
|
||||
@@ -48,48 +56,16 @@ public static class IntroCutscenePatches
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(IntroCutscene.BeginCrewmate))]
|
||||
public static bool BeginCrewmatePatch(IntroCutscene __instance)
|
||||
{
|
||||
if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole customRole)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (customRole.IntroConfiguration is { } introConfig)
|
||||
{
|
||||
__instance.BackgroundBar.material.SetColor(ShaderID.Color, introConfig.IntroTeamColor);
|
||||
__instance.TeamTitle.color = introConfig.IntroTeamColor;
|
||||
__instance.TeamTitle.text = introConfig.IntroTeamTitle;
|
||||
__instance.ImpostorText.text = introConfig.IntroTeamDescription;
|
||||
}
|
||||
|
||||
if (customRole.Team is not ModdedRoleTeams.Custom)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var barTransform = __instance.BackgroundBar.transform;
|
||||
var position = barTransform.position;
|
||||
position.y -= 0.25f;
|
||||
barTransform.position = position;
|
||||
|
||||
__instance.impostorScale = 1f;
|
||||
|
||||
__instance.ourCrewmate = __instance.CreatePlayer(
|
||||
0,
|
||||
Mathf.CeilToInt(7.5f),
|
||||
PlayerControl.LocalPlayer.Data,
|
||||
false);
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(IntroCutscene.OnDestroy))]
|
||||
public static void GameBeginPatch()
|
||||
[HarmonyPatch(typeof(IntroCutscene._CoBegin_d__34), nameof(IntroCutscene._CoBegin_d__34.MoveNext))]
|
||||
public static void GameBeginPatch(IntroCutscene._CoBegin_d__34 __instance)
|
||||
{
|
||||
CustomGameModeManager.ActiveMode?.Initialize();
|
||||
}*/
|
||||
if (__instance.__1__state != -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance.__4__this));
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(Minigame), nameof(Minigame.Begin))]
|
||||
internal static class MinigameBeginErrorPatch
|
||||
{
|
||||
public static void Prefix(Minigame __instance)
|
||||
{
|
||||
__instance.logger ??= new Logger(Logger.Category.Gameplay, "Minigame");
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Modifiers;
|
||||
|
||||
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CoEndGame))]
|
||||
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnGameEnd))]
|
||||
public static class EndGameDidWinPatch
|
||||
{
|
||||
public static void Prefix()
|
||||
public static void Postfix()
|
||||
{
|
||||
var gameOverReason = EndGameResult.CachedGameOverReason;
|
||||
EndGameResult.CachedWinners.Clear();
|
||||
@@ -17,18 +19,26 @@ public static class EndGameDidWinPatch
|
||||
for (var i = 0; i < GameData.Instance.PlayerCount; i++)
|
||||
{
|
||||
var networkedPlayerInfo = players[i];
|
||||
if (networkedPlayerInfo == null)
|
||||
if (!networkedPlayerInfo)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var didWin = networkedPlayerInfo.Role.DidWin(gameOverReason);
|
||||
|
||||
var modifierWin = networkedPlayerInfo.Object?.GetModifierComponent()?.ActiveModifiers.OfType<GameModifier>()
|
||||
.FirstOrDefault(x => x.DidWin(gameOverReason) != null)?.DidWin(gameOverReason);
|
||||
if (modifierWin != null)
|
||||
bool? modifierDidWin = null;
|
||||
foreach (var modifier in networkedPlayerInfo.Object.GetModifiers<GameModifier>().OrderByDescending(x => x.Priority()))
|
||||
{
|
||||
didWin = modifierWin.Value;
|
||||
var result = modifier.DidWin(gameOverReason);
|
||||
if (!result.HasValue) continue;
|
||||
|
||||
modifierDidWin = result.Value;
|
||||
break;
|
||||
}
|
||||
|
||||
if (modifierDidWin.HasValue)
|
||||
{
|
||||
didWin = modifierDidWin.Value;
|
||||
}
|
||||
|
||||
if (didWin)
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
@@ -30,64 +34,98 @@ public static class GameOptionsMenuPatch
|
||||
}
|
||||
|
||||
var num = 2.1f;
|
||||
var filteredGroups =
|
||||
GameSettingMenuPatches.SelectedMod?.OptionGroups.Where(
|
||||
x => x.AdvancedRole is null) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
if (__instance.name == "MODIFIERS TAB")
|
||||
{
|
||||
if (group.Options.Count == 0 || group.Header is null)
|
||||
ModifiersUpdate(ref num);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filteredGroups =
|
||||
GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!group.GroupVisible.Invoke())
|
||||
{
|
||||
group.Header.gameObject.SetActive(false);
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
option.OptionBehaviour?.gameObject.SetActive(false);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
group.Header.gameObject.SetActive(true);
|
||||
group.Header.transform.localScale = Vector3.one * 0.63f;
|
||||
group.Header.transform.localPosition = new Vector3(-0.903f, num, -2f);
|
||||
|
||||
num -= 0.58f;
|
||||
|
||||
foreach (var opt in group.Options)
|
||||
{
|
||||
var newOpt = opt.OptionBehaviour;
|
||||
|
||||
if (newOpt is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!opt.Visible.Invoke())
|
||||
{
|
||||
newOpt.gameObject.SetActive(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!group.AllOptionsHidden)
|
||||
{
|
||||
newOpt.gameObject.SetActive(true);
|
||||
newOpt.transform.localPosition = new Vector3(0.952f, num, -2f);
|
||||
num -= 0.45f;
|
||||
}
|
||||
else
|
||||
{
|
||||
newOpt.gameObject.SetActive(false);
|
||||
}
|
||||
UpdateGroup(group, ref num);
|
||||
}
|
||||
}
|
||||
|
||||
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
|
||||
}
|
||||
|
||||
private static void UpdateGroup(AbstractOptionGroup group, ref float num)
|
||||
{
|
||||
if (group.Options.Count == 0 || group.Header is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!group.GroupVisible.Invoke())
|
||||
{
|
||||
group.Header.gameObject.SetActive(false);
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
option.OptionBehaviour?.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
group.Header.gameObject.SetActive(true);
|
||||
group.Header.transform.localScale = Vector3.one * 0.63f;
|
||||
group.Header.transform.localPosition = new Vector3(-0.903f, num, -2f);
|
||||
|
||||
num -= 0.58f;
|
||||
|
||||
foreach (var opt in group.Options)
|
||||
{
|
||||
var newOpt = opt.OptionBehaviour;
|
||||
|
||||
if (newOpt is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!opt.Visible.Invoke())
|
||||
{
|
||||
newOpt.gameObject.SetActive(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!group.AllOptionsHidden)
|
||||
{
|
||||
newOpt.gameObject.SetActive(true);
|
||||
newOpt.transform.localPosition = new Vector3(0.952f, num, -2f);
|
||||
num -= 0.45f;
|
||||
}
|
||||
else
|
||||
{
|
||||
newOpt.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ModifiersUpdate(ref float num)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
|
||||
|
||||
foreach (var modGroup in groups)
|
||||
{
|
||||
UpdateGroup(modGroup, ref num);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ModifiersCreate(GameOptionsMenu menu)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
.Where(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true) ?? [];
|
||||
foreach (var group in groups)
|
||||
{
|
||||
CreateGroup(menu, group);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
|
||||
public static bool SettingsPatch(GameOptionsMenu __instance)
|
||||
@@ -99,106 +137,18 @@ public static class GameOptionsMenuPatch
|
||||
|
||||
__instance.MapPicker.gameObject.SetActive(false);
|
||||
|
||||
var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups.Where(x => x.AdvancedRole is null) ?? [];
|
||||
if (__instance.name == "MODIFIERS TAB")
|
||||
{
|
||||
ModifiersCreate(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
var filteredGroups = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
{
|
||||
var categoryHeaderMasked = Object.Instantiate(
|
||||
__instance.categoryHeaderOrigin,
|
||||
Vector3.zero,
|
||||
Quaternion.identity,
|
||||
__instance.settingsContainer);
|
||||
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
|
||||
if (group.GroupColor != Color.clear)
|
||||
{
|
||||
categoryHeaderMasked.Background.color = group.GroupColor;
|
||||
categoryHeaderMasked.Divider.color = group.GroupColor;
|
||||
categoryHeaderMasked.Title.color = group.GroupColor.GetAlternateColor();
|
||||
}
|
||||
|
||||
categoryHeaderMasked.Background.size = new Vector2(
|
||||
categoryHeaderMasked.Background.size.x + 1.5f,
|
||||
categoryHeaderMasked.Background.size.y);
|
||||
categoryHeaderMasked.gameObject.SetActive(false);
|
||||
group.Header = categoryHeaderMasked;
|
||||
|
||||
var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform);
|
||||
newText.text = "<size=70%>(Click to close)</size>";
|
||||
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
|
||||
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
|
||||
var options = group.Options.Select(
|
||||
opt => opt.CreateOption(
|
||||
__instance.checkboxOrigin,
|
||||
__instance.numberOptionOrigin,
|
||||
__instance.stringOptionOrigin,
|
||||
__instance.settingsContainer));
|
||||
|
||||
foreach (var newOpt in options)
|
||||
{
|
||||
newOpt.SetClickMask(__instance.ButtonClickMask);
|
||||
|
||||
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
|
||||
foreach (var renderer in componentsInChildren)
|
||||
{
|
||||
if (group.GroupColor != Color.clear)
|
||||
{
|
||||
renderer.color = group.GroupColor.GetAlternateColor();
|
||||
if (renderer.transform.parent.TryGetComponent<GameOptionButton>(out var btn))
|
||||
{
|
||||
btn.interactableColor = group.GroupColor.GetAlternateColor();
|
||||
btn.interactableHoveredColor = Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
renderer.material.SetInt(PlayerMaterial.MaskLayer, 20);
|
||||
}
|
||||
|
||||
foreach (var textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
|
||||
{
|
||||
if (group.GroupColor != Color.clear)
|
||||
{
|
||||
textMeshPro.color = group.GroupColor;
|
||||
}
|
||||
|
||||
textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f);
|
||||
textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20);
|
||||
}
|
||||
|
||||
if (newOpt is ToggleOption toggle)
|
||||
{
|
||||
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
|
||||
toggle.CheckMark.color =
|
||||
group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
|
||||
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite")
|
||||
.GetComponent<SpriteRenderer>();
|
||||
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
|
||||
rend.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
|
||||
}
|
||||
|
||||
__instance.Children.Add(newOpt);
|
||||
|
||||
newOpt.Initialize();
|
||||
newOpt.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
var boxCol = categoryHeaderMasked.gameObject.AddComponent<BoxCollider2D>();
|
||||
boxCol.size = new Vector2(7, 0.7f);
|
||||
boxCol.offset = new Vector2(1.5f, -0.3f);
|
||||
|
||||
var headerBtn = categoryHeaderMasked.gameObject.AddComponent<PassiveButton>();
|
||||
headerBtn.ClickSound = __instance.BackButton.GetComponent<PassiveButton>().ClickSound;
|
||||
headerBtn.OnMouseOver = new UnityEvent();
|
||||
headerBtn.OnMouseOut = new UnityEvent();
|
||||
headerBtn.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
group.AllOptionsHidden = !group.AllOptionsHidden;
|
||||
newText.text = group.AllOptionsHidden
|
||||
? "<size=70%>(Click to open)</size>"
|
||||
: "<size=70%>(Click to close)</size>";
|
||||
}));
|
||||
headerBtn.SetButtonEnableState(true);
|
||||
CreateGroup(__instance, group);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -233,9 +183,112 @@ public static class GameOptionsMenuPatch
|
||||
continue;
|
||||
}
|
||||
|
||||
optionBehaviour.OnValueChanged = new System.Action<OptionBehaviour>(__instance.ValueChanged);
|
||||
optionBehaviour.OnValueChanged = new Action<OptionBehaviour>(__instance.ValueChanged);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void CreateGroup(GameOptionsMenu menu, AbstractOptionGroup group)
|
||||
{
|
||||
var categoryHeaderMasked = Object.Instantiate(
|
||||
menu.categoryHeaderOrigin,
|
||||
Vector3.zero,
|
||||
Quaternion.identity,
|
||||
menu.settingsContainer);
|
||||
|
||||
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
|
||||
categoryHeaderMasked.Background.color = group.GroupColor;
|
||||
categoryHeaderMasked.Divider.color = group.GroupColor;
|
||||
categoryHeaderMasked.Title.color = group.GroupColor.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.GroupColor.FindAlternateColor();
|
||||
|
||||
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
|
||||
categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear;
|
||||
categoryHeaderMasked.Background.sprite.texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0);
|
||||
categoryHeaderMasked.Background.size = new Vector2(
|
||||
categoryHeaderMasked.Background.size.x + 1.5f,
|
||||
categoryHeaderMasked.Background.size.y);
|
||||
|
||||
categoryHeaderMasked.gameObject.SetActive(false);
|
||||
group.Header = categoryHeaderMasked;
|
||||
|
||||
var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform);
|
||||
newText.text = "<size=70%>(Click to close)</size>";
|
||||
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
|
||||
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
|
||||
var options = group.Options.Select(
|
||||
opt => opt.CreateOption(
|
||||
menu.checkboxOrigin,
|
||||
menu.numberOptionOrigin,
|
||||
menu.stringOptionOrigin,
|
||||
menu.settingsContainer));
|
||||
|
||||
foreach (var newOpt in options)
|
||||
{
|
||||
newOpt.SetClickMask(menu.ButtonClickMask);
|
||||
|
||||
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
|
||||
foreach (var renderer in componentsInChildren)
|
||||
{
|
||||
if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor)
|
||||
{
|
||||
renderer.color = group.GroupColor.FindAlternateColor();
|
||||
if (renderer.transform.parent.TryGetComponent<GameOptionButton>(out var btn))
|
||||
{
|
||||
btn.interactableColor = group.GroupColor.FindAlternateColor();
|
||||
btn.interactableHoveredColor = Color.white;
|
||||
}
|
||||
}
|
||||
|
||||
renderer.material.SetInt(PlayerMaterial.MaskLayer, 20);
|
||||
}
|
||||
|
||||
foreach (var textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
|
||||
{
|
||||
if (group.GroupColor != MiraApiPlugin.DefaultHeaderColor)
|
||||
{
|
||||
textMeshPro.color = group.GroupColor;
|
||||
}
|
||||
|
||||
textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f);
|
||||
textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20);
|
||||
}
|
||||
|
||||
if (newOpt is ToggleOption toggle)
|
||||
{
|
||||
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
|
||||
toggle.CheckMark.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
|
||||
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite")
|
||||
.GetComponent<SpriteRenderer>();
|
||||
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
|
||||
rend.color = group.GroupColor != MiraApiPlugin.DefaultHeaderColor ? group.GroupColor : MiraAssets.AcceptedTeal;
|
||||
}
|
||||
|
||||
menu.Children.Add(newOpt);
|
||||
|
||||
newOpt.Initialize();
|
||||
newOpt.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
var boxCol = categoryHeaderMasked.gameObject.AddComponent<BoxCollider2D>();
|
||||
boxCol.size = new Vector2(7, 0.7f);
|
||||
boxCol.offset = new Vector2(1.5f, -0.3f);
|
||||
|
||||
var headerBtn = categoryHeaderMasked.gameObject.AddComponent<PassiveButton>();
|
||||
headerBtn.ClickSound = menu.BackButton.GetComponent<PassiveButton>().ClickSound;
|
||||
headerBtn.OnMouseOver = new UnityEvent();
|
||||
headerBtn.OnMouseOut = new UnityEvent();
|
||||
headerBtn.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
group.AllOptionsHidden = !group.AllOptionsHidden;
|
||||
newText.text = group.AllOptionsHidden
|
||||
? "<size=70%>(Click to open)</size>"
|
||||
: "<size=70%>(Click to close)</size>";
|
||||
}));
|
||||
headerBtn.SetButtonEnableState(true);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
@@ -12,25 +13,64 @@ using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Patches for the <see cref="GameSettingMenu"/> to add support for custom options.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(GameSettingMenu))]
|
||||
public static class GameSettingMenuPatches
|
||||
internal static class GameSettingMenuPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the currently selected mod index.
|
||||
/// </summary>
|
||||
public static int SelectedModIdx { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently selected mod.
|
||||
/// </summary>
|
||||
public static MiraPluginInfo? SelectedMod { get; private set; }
|
||||
|
||||
private static TextMeshPro? _text;
|
||||
|
||||
private static Vector3 _roleBtnOgPos;
|
||||
private static Vector3 _smallRoleBtnOgPos;
|
||||
private static Vector3 _modifierBtnOgPos;
|
||||
|
||||
private static GameOptionsMenu? _modifiersTab;
|
||||
private static PassiveButton? _modifiersButton;
|
||||
private static PassiveButton? _smallRolesButton;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab))]
|
||||
public static void ChangeTabPostfix(GameSettingMenu __instance, int tabNum, bool previewOnly)
|
||||
{
|
||||
if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly)
|
||||
{
|
||||
if (_modifiersTab) _modifiersTab!.gameObject.SetActive(tabNum == 3);
|
||||
_modifiersButton?.SelectButton(tabNum == 3);
|
||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||
if (tabNum == 3)
|
||||
{
|
||||
if (__instance.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
__instance.RoleSettingsButton.SelectButton(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiersButton?.SelectButton(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (previewOnly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_modifiersButton?.SelectButton(tabNum == 3);
|
||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||
|
||||
if (tabNum != 3) return;
|
||||
|
||||
if (__instance.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
__instance.RoleSettingsButton.SelectButton(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiersButton?.SelectButton(true);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prefix for the <see cref="GameSettingMenu.Start"/> method. Sets up the custom options.
|
||||
@@ -54,45 +94,157 @@ public static class GameSettingMenuPatches
|
||||
_text.fontSizeMax = 3.2f;
|
||||
_text.overflowMode = TextOverflowModes.Overflow;
|
||||
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
|
||||
_text.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
|
||||
nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
|
||||
nextButton.name = "RightArrowButton";
|
||||
nextButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite = MiraAssets.NextButton.LoadAsset();
|
||||
nextButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite = MiraAssets.NextButtonActive.LoadAsset();
|
||||
nextButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||
MiraAssets.NextButton.LoadAsset();
|
||||
nextButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||
MiraAssets.NextButtonActive.LoadAsset();
|
||||
nextButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
||||
|
||||
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
|
||||
passiveButton.OnClick = new ButtonClickedEvent();
|
||||
passiveButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
passiveButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
SelectedModIdx = 0;
|
||||
}
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
{
|
||||
SelectedModIdx = 0;
|
||||
}
|
||||
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
|
||||
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
|
||||
backButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
|
||||
backButton.name = "LeftArrowButton";
|
||||
backButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
|
||||
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
||||
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
|
||||
backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
||||
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
{
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
}
|
||||
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
|
||||
// clone game settings tab for modifiers
|
||||
_modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||
_modifiersTab.name = "MODIFIERS TAB";
|
||||
|
||||
// create button for modifiers
|
||||
__instance.RoleSettingsButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
__instance.RoleSettingsButton.gameObject.SetActive(false);
|
||||
_smallRolesButton = Object.Instantiate(
|
||||
__instance.RoleSettingsButton,
|
||||
__instance.RoleSettingsButton.transform.parent);
|
||||
var pos = new Vector3(
|
||||
-3.65f,
|
||||
_smallRolesButton.transform.localPosition.y,
|
||||
_smallRolesButton.transform.localPosition.z);
|
||||
_smallRolesButton.transform.localPosition = pos;
|
||||
_smallRolesButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(2, false);
|
||||
}));
|
||||
_smallRolesButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(2, true);
|
||||
}));
|
||||
|
||||
_smallRoleBtnOgPos = _smallRolesButton.transform.localPosition;
|
||||
|
||||
var roleText = _smallRolesButton.buttonText;
|
||||
roleText.text = "Roles";
|
||||
roleText.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
roleText.alignment = TextAlignmentOptions.Center;
|
||||
roleText.transform.parent.localPosition = new Vector3(
|
||||
-.525f,
|
||||
roleText.transform.parent.localPosition.y,
|
||||
roleText.transform.parent.localPosition.z);
|
||||
|
||||
foreach (var collider in _smallRolesButton.Colliders)
|
||||
{
|
||||
if (collider.TryCast<BoxCollider2D>() is { } col)
|
||||
{
|
||||
col.size = new Vector2(col.size.x / 2, col.size.y);
|
||||
}
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
}
|
||||
|
||||
foreach (var rend in _smallRolesButton.GetComponentsInChildren<SpriteRenderer>(true))
|
||||
{
|
||||
rend.size = new Vector2(rend.size.x / 2, rend.size.y);
|
||||
}
|
||||
|
||||
_modifiersButton = Object.Instantiate(_smallRolesButton, _smallRolesButton.transform.parent);
|
||||
_modifiersButton.OnClick = new ButtonClickedEvent();
|
||||
_modifiersButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(3, false);
|
||||
}));
|
||||
_modifiersButton.OnMouseOver = new UnityEvent();
|
||||
_modifiersButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(3, true);
|
||||
}));
|
||||
|
||||
_modifiersButton.buttonText.text = "Modifiers";
|
||||
pos.x = -2.27f;
|
||||
_modifiersButton.transform.localPosition = pos;
|
||||
_modifiersButton.name = "ModifiersButton";
|
||||
|
||||
_modifierBtnOgPos = _modifiersButton.transform.localPosition;
|
||||
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}
|
||||
|
||||
private static void ChangeRoleSettingButton(bool replace, GameSettingMenu __instance)
|
||||
{
|
||||
__instance.RoleSettingsButton.OnClick = new ButtonClickedEvent();
|
||||
__instance.RoleSettingsButton.OnMouseOver = new UnityEvent();
|
||||
|
||||
if (replace)
|
||||
{
|
||||
__instance.RoleSettingsButton.buttonText.text = "Modifiers Settings";
|
||||
__instance.RoleSettingsButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(3, false);
|
||||
}));
|
||||
__instance.RoleSettingsButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(3, true);
|
||||
}));
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.RoleSettingsButton.buttonText.text = "Roles Settings";
|
||||
__instance.RoleSettingsButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(2, false);
|
||||
}));
|
||||
__instance.RoleSettingsButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(2, true);
|
||||
}));
|
||||
}
|
||||
}
|
||||
private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles)
|
||||
{
|
||||
if (_text is not null && SelectedModIdx == 0)
|
||||
@@ -109,86 +261,182 @@ public static class GameSettingMenuPatches
|
||||
_text.text = name[..Math.Min(name.Length, 25)];
|
||||
}
|
||||
|
||||
bool replaceWithModifiers = true;
|
||||
_smallRolesButton!.transform.localPosition = _smallRoleBtnOgPos;
|
||||
_modifiersButton!.transform.localPosition = _modifierBtnOgPos;
|
||||
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
|
||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
|
||||
if (SelectedModIdx != 0)
|
||||
{
|
||||
if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0)
|
||||
var modHasRoles = SelectedMod!.CustomRoles.Count != 0;
|
||||
var modHasModifiers = SelectedMod.OptionGroups.Exists(x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
||||
var modHasOptions = SelectedMod.OptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
|
||||
|
||||
_modifiersButton.gameObject.SetActive(true);
|
||||
_smallRolesButton.gameObject.SetActive(true);
|
||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(false);
|
||||
|
||||
// If there are no registered custom roles in the selected mod, hide the button.
|
||||
if (!modHasRoles)
|
||||
{
|
||||
_smallRolesButton.gameObject.SetActive(false);
|
||||
|
||||
if (roles.gameObject.active)
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
|
||||
// If the mod has modifiers, we can disable the smaller modifier button and enable the bigger one.
|
||||
if (modHasModifiers)
|
||||
{
|
||||
_modifiersButton.gameObject.SetActive(false);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
|
||||
if (_modifiersTab!.gameObject.active)
|
||||
{
|
||||
menu.RoleSettingsButton.SelectButton(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no modifiers in the selected mod, hide the modifiers button.
|
||||
if (!modHasModifiers)
|
||||
{
|
||||
replaceWithModifiers = false;
|
||||
_modifiersButton.gameObject.SetActive(false);
|
||||
|
||||
if (_modifiersTab!.gameObject.active)
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
// If the mod has roles, we can enable the bigger role button and disable the small one.
|
||||
if (modHasRoles)
|
||||
{
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
_smallRolesButton.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no custom game options registered (that aren't modifier options), hide game settings button.
|
||||
if (!modHasOptions)
|
||||
{
|
||||
menu.GameSettingsButton.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
if (SelectedMod?.CustomRoles.Count == 0)
|
||||
{
|
||||
menu.RoleSettingsButton.gameObject.SetActive(false);
|
||||
}
|
||||
if (settings.gameObject.active)
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
|
||||
if (!menu.GameSettingsButton.gameObject.active && menu.RoleSettingsButton.gameObject.active)
|
||||
// If the mod has roles and modifiers, we can move their buttons to the game settings button position, since nothing is there.
|
||||
if (menu.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
menu.RoleSettingsButton.transform.localPosition = menu.GameSettingsButton.transform.localPosition;
|
||||
}
|
||||
else if (_modifiersButton.gameObject.active && _smallRolesButton.gameObject.active)
|
||||
{
|
||||
_modifiersButton.transform.localPosition = new Vector3(
|
||||
_modifiersButton.transform.localPosition.x,
|
||||
menu.GameSettingsButton.transform.localPosition.y,
|
||||
_modifiersButton.transform.localPosition.z);
|
||||
|
||||
_smallRolesButton.transform.localPosition = new Vector3(
|
||||
_smallRolesButton.transform.localPosition.x,
|
||||
menu.GameSettingsButton.transform.localPosition.y,
|
||||
_smallRolesButton.transform.localPosition.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiersButton.gameObject.SetActive(false);
|
||||
_smallRolesButton.gameObject.SetActive(false);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||
replaceWithModifiers = false;
|
||||
|
||||
if (_modifiersTab!.gameObject.active)
|
||||
{
|
||||
menu.RoleSettingsButton.transform.localPosition = menu.GameSettingsButton.transform.localPosition;
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (roles.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0)
|
||||
if (menu.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
if (roles.advancedSettingChildren is not null)
|
||||
{
|
||||
foreach (var child in roles.advancedSettingChildren)
|
||||
{
|
||||
child.gameObject.DestroyImmediate();
|
||||
}
|
||||
roles.advancedSettingChildren.Clear();
|
||||
roles.advancedSettingChildren = null;
|
||||
}
|
||||
|
||||
foreach (var header in roles.RoleChancesSettings.transform.GetComponentsInChildren<CategoryHeaderEditRole>())
|
||||
{
|
||||
header.gameObject.DestroyImmediate();
|
||||
}
|
||||
|
||||
foreach (var roleChance in roles.roleChances)
|
||||
{
|
||||
roleChance.gameObject.DestroyImmediate();
|
||||
}
|
||||
|
||||
roles.roleChances.Clear();
|
||||
roles.AdvancedRolesSettings.gameObject.SetActive(false);
|
||||
roles.RoleChancesSettings.gameObject.SetActive(true);
|
||||
roles.SetQuotaTab();
|
||||
|
||||
roles.scrollBar.CalculateAndSetYBounds(roles.roleChances.Count + 5, 1f, 6f, 0.43f);
|
||||
roles.scrollBar.ScrollToTop();
|
||||
ChangeRoleSettingButton(replaceWithModifiers, menu);
|
||||
}
|
||||
|
||||
if (settings.Children != null && SelectedMod?.OptionGroups.Count != 0)
|
||||
CleanupTab(settings, roles);
|
||||
}
|
||||
|
||||
private static void ClearOptions(Il2CppSystem.Collections.Generic.List<OptionBehaviour> options)
|
||||
{
|
||||
foreach (var child in options)
|
||||
{
|
||||
foreach (var child in settings.Children)
|
||||
if (child.TryCast<GameOptionsMapPicker>() || !child.gameObject)
|
||||
{
|
||||
if (child.TryCast<GameOptionsMapPicker>())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
child.gameObject.DestroyImmediate();
|
||||
}
|
||||
options.Clear();
|
||||
}
|
||||
|
||||
if (!child.gameObject)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
child.gameObject.DestroyImmediate();
|
||||
private static void CleanupTab(GameOptionsMenu settings, RolesSettingsMenu roles)
|
||||
{
|
||||
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
|
||||
{
|
||||
if (rolesMenu.advancedSettingChildren != null)
|
||||
{
|
||||
ClearOptions(rolesMenu.advancedSettingChildren);
|
||||
rolesMenu.advancedSettingChildren = null;
|
||||
}
|
||||
|
||||
foreach (var header in settings.settingsContainer.GetComponentsInChildren<CategoryHeaderMasked>())
|
||||
rolesMenu.RoleChancesSettings
|
||||
.transform
|
||||
.GetComponentsInChildren<CategoryHeaderEditRole>()
|
||||
.ToList()
|
||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||
|
||||
foreach (var role in rolesMenu.roleChances)
|
||||
{
|
||||
header.gameObject.DestroyImmediate();
|
||||
role.gameObject.DestroyImmediate();
|
||||
}
|
||||
rolesMenu.roleChances?.Clear();
|
||||
|
||||
settings.Children.Clear();
|
||||
settings.Children = null;
|
||||
rolesMenu.AdvancedRolesSettings.gameObject.SetActive(false);
|
||||
rolesMenu.RoleChancesSettings.gameObject.SetActive(true);
|
||||
rolesMenu.SetQuotaTab();
|
||||
rolesMenu.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
settings.Initialize();
|
||||
settings.scrollBar.ScrollToTop();
|
||||
void CleanupSettings(GameOptionsMenu gameOptMenu)
|
||||
{
|
||||
ClearOptions(gameOptMenu.Children);
|
||||
gameOptMenu.Children = null;
|
||||
|
||||
gameOptMenu.settingsContainer
|
||||
.GetComponentsInChildren<CategoryHeaderMasked>()
|
||||
.ToList()
|
||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||
|
||||
gameOptMenu.Initialize();
|
||||
gameOptMenu.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
if (roles.roleChances != null && SelectedMod?.CustomRoles.Count > 0)
|
||||
{
|
||||
CleanupRoleSettings(roles);
|
||||
}
|
||||
|
||||
if (settings.Children != null && SelectedMod?.OptionGroups.Count > 0)
|
||||
{
|
||||
CleanupSettings(settings);
|
||||
}
|
||||
|
||||
if (_modifiersTab?.Children?.Count > 0)
|
||||
{
|
||||
CleanupSettings(_modifiersTab);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
@@ -16,17 +20,38 @@ namespace MiraAPI.Patches.Options;
|
||||
[HarmonyPatch(typeof(LobbyViewSettingsPane))]
|
||||
public static class LobbyViewPanePatches
|
||||
{
|
||||
private static int CurrentSelectedMod { get; set; }
|
||||
private static int SelectedModIdx { get; set; }
|
||||
|
||||
private static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0
|
||||
private static MiraPluginInfo? SelectedMod => SelectedModIdx == 0
|
||||
? null
|
||||
: MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod - 1];
|
||||
: MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
|
||||
|
||||
private static PassiveButton? ModifiersTabButton { get; set; }
|
||||
|
||||
private static StringNames ModifiersTabName { get; } = CustomStringName.CreateAndRegister("ModifiersTab");
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))]
|
||||
public static void AwakePatch(LobbyViewSettingsPane __instance)
|
||||
{
|
||||
__instance.gameModeText.transform.localPosition = new Vector3(-2.3f, 2.4f, -2);
|
||||
__instance.gameModeText.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
|
||||
// create modifiers button
|
||||
ModifiersTabButton = Object.Instantiate(__instance.rolesTabButton, __instance.rolesTabButton.transform.parent);
|
||||
ModifiersTabButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
ModifiersTabButton.name = "ModifiersTabButton";
|
||||
var pos = ModifiersTabButton.transform.localPosition;
|
||||
pos.x = 2.1f;
|
||||
ModifiersTabButton.transform.localPosition = pos;
|
||||
ModifiersTabButton.buttonText.text = "Modifiers";
|
||||
ModifiersTabButton.OnClick = new Button.ButtonClickedEvent();
|
||||
ModifiersTabButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(ModifiersTabName);
|
||||
}));
|
||||
ModifiersTabButton.gameObject.SetActive(SelectedModIdx!=0);
|
||||
|
||||
// Create the next button
|
||||
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
|
||||
@@ -48,14 +73,13 @@ public static class LobbyViewPanePatches
|
||||
passiveButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
CurrentSelectedMod += 1;
|
||||
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
{
|
||||
CurrentSelectedMod = 0;
|
||||
SelectedModIdx = 0;
|
||||
}
|
||||
|
||||
__instance.RefreshTab();
|
||||
__instance.scrollBar.ScrollToTop();
|
||||
Refresh(__instance);
|
||||
}));
|
||||
|
||||
// Create the back button
|
||||
@@ -71,34 +95,56 @@ public static class LobbyViewPanePatches
|
||||
passiveButton2.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
CurrentSelectedMod -= 1;
|
||||
if (CurrentSelectedMod < 0)
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
{
|
||||
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
}
|
||||
|
||||
__instance.RefreshTab();
|
||||
__instance.scrollBar.ScrollToTop();
|
||||
Refresh(__instance);
|
||||
}));
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.Update))]
|
||||
public static void UpdatePatch(LobbyViewSettingsPane __instance)
|
||||
private static void Refresh(LobbyViewSettingsPane menu)
|
||||
{
|
||||
__instance.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default";
|
||||
ModifiersTabButton?.gameObject.SetActive(SelectedModIdx != 0);
|
||||
menu.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default";
|
||||
menu.RefreshTab();
|
||||
menu.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.SetTab))]
|
||||
public static bool SetTabPatch(LobbyViewSettingsPane __instance)
|
||||
{
|
||||
if (__instance.currentTab != ModifiersTabName || SelectedMod == null)
|
||||
{
|
||||
ModifiersTabButton?.SelectButton(false);
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.taskTabButton.SelectButton(false);
|
||||
__instance.rolesTabButton.SelectButton(false);
|
||||
|
||||
ModifiersTabButton?.SelectButton(true);
|
||||
var filteredGroups = SelectedMod.OptionGroups
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true));
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.DrawNormalTab))]
|
||||
public static bool DrawNormalTabPatch(LobbyViewSettingsPane __instance)
|
||||
{
|
||||
if (CurrentSelectedMod == 0)
|
||||
if (SelectedModIdx == 0 || SelectedMod == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
DrawOptionsTab(__instance);
|
||||
var filteredGroups = SelectedMod.OptionGroups
|
||||
.Where(x => x.OptionableType == null && x.GroupVisible.Invoke());
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -106,7 +152,7 @@ public static class LobbyViewPanePatches
|
||||
[HarmonyPatch(nameof(LobbyViewSettingsPane.DrawRolesTab))]
|
||||
public static bool DrawRolesTabPatch(LobbyViewSettingsPane __instance)
|
||||
{
|
||||
if (CurrentSelectedMod == 0)
|
||||
if (SelectedModIdx == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -115,29 +161,22 @@ public static class LobbyViewPanePatches
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void DrawOptionsTab(LobbyViewSettingsPane instance)
|
||||
private static void DrawOptions(LobbyViewSettingsPane menu, IEnumerable<AbstractOptionGroup> groups)
|
||||
{
|
||||
if (SelectedMod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var num = 1.44f;
|
||||
|
||||
var filteredGroups = SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null);
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var categoryHeaderMasked = Object.Instantiate(
|
||||
instance.categoryHeaderOrigin,
|
||||
instance.settingsContainer,
|
||||
menu.categoryHeaderOrigin,
|
||||
menu.settingsContainer,
|
||||
true);
|
||||
categoryHeaderMasked.SetHeader(StringNames.Name, 61);
|
||||
categoryHeaderMasked.Title.text = group.GroupName;
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
|
||||
num -= 0.85f;
|
||||
menu.settingsInfo.Add(categoryHeaderMasked.gameObject);
|
||||
num -= 1.05f;
|
||||
|
||||
var i = 0;
|
||||
|
||||
@@ -149,8 +188,8 @@ public static class LobbyViewPanePatches
|
||||
}
|
||||
|
||||
var viewSettingsInfoPanel = Object.Instantiate(
|
||||
instance.infoPanelOrigin,
|
||||
instance.settingsContainer,
|
||||
menu.infoPanelOrigin,
|
||||
menu.settingsContainer,
|
||||
true);
|
||||
viewSettingsInfoPanel.transform.localScale = Vector3.one;
|
||||
float num2;
|
||||
@@ -159,7 +198,7 @@ public static class LobbyViewPanePatches
|
||||
num2 = -8.95f;
|
||||
if (i > 0)
|
||||
{
|
||||
num -= 0.59f;
|
||||
num -= 0.85f;
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -183,14 +222,14 @@ public static class LobbyViewPanePatches
|
||||
viewSettingsInfoPanel.SetInfo(data.Title, data.GetValueString(option.GetFloatData()), 61);
|
||||
}
|
||||
|
||||
instance.settingsInfo.Add(viewSettingsInfoPanel.gameObject);
|
||||
menu.settingsInfo.Add(viewSettingsInfoPanel.gameObject);
|
||||
i++;
|
||||
}
|
||||
|
||||
num -= 0.59f;
|
||||
num -= 0.85f;
|
||||
}
|
||||
|
||||
instance.scrollBar.CalculateAndSetYBounds(instance.settingsInfo.Count + 10, 2f, 6f, 0.59f);
|
||||
menu.scrollBar.CalculateAndSetYBounds(menu.settingsInfo.Count + 10, 2f, 6f, 0.85f);
|
||||
}
|
||||
|
||||
private static void DrawRolesTab(LobbyViewSettingsPane instance)
|
||||
@@ -211,34 +250,38 @@ public static class LobbyViewPanePatches
|
||||
|
||||
var list = new List<Type>();
|
||||
|
||||
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
|
||||
{
|
||||
var filteredRoles = SelectedMod.CustomRoles.Values
|
||||
.OfType<ICustomRole>()
|
||||
.Where(x => !x.Configuration.HideSettings && x.Team == team).ToList();
|
||||
var roleGroups = SelectedMod.CustomRoles.Values.OfType<ICustomRole>()
|
||||
.ToLookup(x => x.RoleOptionsGroup);
|
||||
|
||||
if (filteredRoles.Count == 0)
|
||||
// sort the groups by priority
|
||||
var sortedRoleGroups = roleGroups
|
||||
.OrderBy(x => x.Key.Priority)
|
||||
.ThenBy(x => x.Key.Name);
|
||||
|
||||
foreach (var grouping in sortedRoleGroups)
|
||||
{
|
||||
if (!grouping.Any() || grouping.All(x => x.Configuration.HideSettings))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var categoryHeaderRoleVariant =
|
||||
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
|
||||
var group = grouping.Key;
|
||||
|
||||
switch (team)
|
||||
var name = group.Name switch
|
||||
{
|
||||
case ModdedRoleTeams.Crewmate:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
|
||||
break;
|
||||
case ModdedRoleTeams.Impostor:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
|
||||
break;
|
||||
default:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
|
||||
categoryHeaderRoleVariant.Title.text = team + " Roles";
|
||||
categoryHeaderRoleVariant.Background.color = Color.gray;
|
||||
categoryHeaderRoleVariant.Title.color = Color.white;
|
||||
break;
|
||||
"Crewmate" => StringNames.CrewmateRolesHeader,
|
||||
"Impostor" => StringNames.ImpostorRolesHeader,
|
||||
_ => CustomStringName.CreateAndRegister(group.Name),
|
||||
};
|
||||
|
||||
var categoryHeaderRoleVariant = Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
|
||||
categoryHeaderRoleVariant.SetHeader(name, 61);
|
||||
|
||||
if (name is not (StringNames.CrewmateRolesHeader or StringNames.ImpostorRolesHeader))
|
||||
{
|
||||
var veryDarkColor = group.Color.DarkenColor(.35f);
|
||||
categoryHeaderRoleVariant.Title.color = veryDarkColor;
|
||||
categoryHeaderRoleVariant.Background.color = group.Color;
|
||||
}
|
||||
|
||||
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
|
||||
@@ -246,7 +289,7 @@ public static class LobbyViewPanePatches
|
||||
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
||||
num -= 0.696f;
|
||||
|
||||
foreach (var customRole in filteredRoles)
|
||||
foreach (var customRole in grouping)
|
||||
{
|
||||
var roleBehaviour = customRole as RoleBehaviour;
|
||||
if (roleBehaviour == null)
|
||||
@@ -259,8 +302,6 @@ public static class LobbyViewPanePatches
|
||||
var numPerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
|
||||
|
||||
var flag = numPerGame == 0;
|
||||
|
||||
var viewSettingsInfoPanelRoleVariant =
|
||||
Object.Instantiate(
|
||||
instance.infoPanelRoleOrigin,
|
||||
@@ -269,11 +310,12 @@ public static class LobbyViewPanePatches
|
||||
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
|
||||
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
|
||||
|
||||
var advancedRoleOptions = SelectedMod.Options
|
||||
.Where(x => x.AdvancedRole == customRole.GetType())
|
||||
var advancedRoleOptions = SelectedMod.OptionGroups
|
||||
.Where(x => x.OptionableType == customRole.GetType())
|
||||
.SelectMany(x => x.Options)
|
||||
.ToList();
|
||||
|
||||
if (!flag && advancedRoleOptions.Count > 0)
|
||||
if (numPerGame > 0 && advancedRoleOptions.Count > 0)
|
||||
{
|
||||
list.Add(customRole.GetType());
|
||||
}
|
||||
@@ -284,14 +326,16 @@ public static class LobbyViewPanePatches
|
||||
chancePerGame,
|
||||
61,
|
||||
customRole.RoleColor,
|
||||
customRole.Configuration.Icon.LoadAsset(),
|
||||
customRole.Configuration.Icon?.LoadAsset() ?? MiraAssets.Empty.LoadAsset(),
|
||||
true);
|
||||
viewSettingsInfoPanelRoleVariant.iconSprite.transform.localScale = new Vector3(0.365f, 0.365f, 1f);
|
||||
viewSettingsInfoPanelRoleVariant.iconSprite.transform.localPosition = new Vector3(0.7144f, -0.028f, -2);
|
||||
|
||||
viewSettingsInfoPanelRoleVariant.titleText.color =
|
||||
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
|
||||
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
|
||||
viewSettingsInfoPanelRoleVariant.background.color =
|
||||
customRole.RoleColor.GetAlternateColor();
|
||||
customRole.RoleColor.FindAlternateColor();
|
||||
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
|
||||
num -= 0.664f;
|
||||
}
|
||||
@@ -369,14 +413,16 @@ public static class LobbyViewPanePatches
|
||||
role.StringName,
|
||||
maskLayer,
|
||||
role.TeamType == RoleTeamTypes.Crewmate,
|
||||
customRole.Configuration.Icon.LoadAsset());
|
||||
customRole.Configuration.Icon != null ? customRole.Configuration.Icon.LoadAsset() : MiraAssets.Empty.LoadAsset());
|
||||
viewPanel.header.icon.transform.localScale = new Vector3(0.465f, 0.465f, 1f);
|
||||
viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer);
|
||||
|
||||
var num = viewPanel.yPosStart;
|
||||
var num2 = 1.08f;
|
||||
|
||||
var filteredOptions = SelectedMod.Options
|
||||
.Where(x => x.AdvancedRole == roleType)
|
||||
var filteredOptions = SelectedMod.OptionGroups
|
||||
.Where(x => x.OptionableType == roleType)
|
||||
.SelectMany(x=>x.Options)
|
||||
.ToList();
|
||||
|
||||
for (var i = 0; i < filteredOptions.Count; i++)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.Roles;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
|
||||
@@ -22,14 +22,21 @@ namespace MiraAPI.Patches.Options;
|
||||
[HarmonyPatch(typeof(RolesSettingsMenu))]
|
||||
public static class RoleSettingMenuPatches
|
||||
{
|
||||
private static Dictionary<RoleOptionsGroup, bool> RoleGroupHidden { get; } = [];
|
||||
private static List<CategoryHeaderEditRole> CategoryHeaderEditRoles { get; } = [];
|
||||
private static List<RoleOptionSetting> RoleOptionSettings { get; } = [];
|
||||
private static Dictionary<RoleOptionsGroup, bool> RoleGroupHidden { get; set; } = new();
|
||||
private static List<GameObject> Headers { get; set; } = new();
|
||||
private static List<RoleOptionSetting> RoleOptionSettings { get; set; } = new();
|
||||
|
||||
private static float ScrollerNum { get; set; } = 0.522f;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
|
||||
public static bool PatchStart(RolesSettingsMenu __instance)
|
||||
{
|
||||
Headers.ForEach(Object.Destroy);
|
||||
RoleOptionSettings.ForEach(Object.Destroy);
|
||||
Headers.Clear();
|
||||
RoleOptionSettings.Clear();
|
||||
|
||||
__instance.roleChances = new Il2CppSystem.Collections.Generic.List<RoleOptionSetting>();
|
||||
__instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List<OptionBehaviour>();
|
||||
|
||||
@@ -48,7 +55,7 @@ public static class RoleSettingMenuPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
var num = 0.522f;
|
||||
ScrollerNum = 0.522f;
|
||||
|
||||
__instance.AllButton.transform.parent.gameObject.SetActive(false);
|
||||
__instance.AllButton.gameObject.SetActive(false);
|
||||
@@ -73,45 +80,100 @@ public static class RoleSettingMenuPatches
|
||||
.OrderBy(x => x.Key.Priority)
|
||||
.ThenBy(x => x.Key.Name);
|
||||
|
||||
var quotaThing = __instance.categoryHeaderEditRoleOrigin.transform.FindChild("QuotaHeader");
|
||||
var template = __instance.transform.parent.parent.GetComponent<GameSettingMenu>().GameSettingsTab.categoryHeaderOrigin;
|
||||
|
||||
foreach (var grouping in sortedRoleGroups)
|
||||
{
|
||||
if (!grouping.Any())
|
||||
if (!grouping.Any() || grouping.All(x=>x.Configuration.HideSettings))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RoleGroupHidden.TryAdd(grouping.Key, false);
|
||||
var group = grouping.Key;
|
||||
|
||||
var name = grouping.Key.Name switch
|
||||
RoleGroupHidden.TryAdd(group, false);
|
||||
|
||||
var name = group.Name switch
|
||||
{
|
||||
"Crewmate" => StringNames.CrewmateRolesHeader,
|
||||
"Impostor" => StringNames.ImpostorRolesHeader,
|
||||
_ => CustomStringName.CreateAndRegister(grouping.Key.Name),
|
||||
_ => CustomStringName.CreateAndRegister(group.Name),
|
||||
};
|
||||
|
||||
var categoryHeaderEditRole = Object.Instantiate(
|
||||
__instance.categoryHeaderEditRoleOrigin,
|
||||
var categoryHeaderMasked = Object.Instantiate(
|
||||
template,
|
||||
Vector3.zero,
|
||||
Quaternion.identity,
|
||||
__instance.RoleChancesSettings.transform);
|
||||
categoryHeaderEditRole.SetHeader(name, 20);
|
||||
categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f);
|
||||
categoryHeaderEditRole.transform.Find("LabelSprite").transform.localScale = new Vector3(1.3f, 1, 0.5529f);
|
||||
categoryHeaderEditRole.transform.Find("QuotaHeader").gameObject.SetActive(!RoleGroupHidden[grouping.Key]);
|
||||
|
||||
CategoryHeaderEditRoles.Add(categoryHeaderEditRole);
|
||||
categoryHeaderMasked.SetHeader(name, 20);
|
||||
|
||||
num -= 0.522f;
|
||||
var quotaInst = Object.Instantiate(quotaThing, categoryHeaderMasked.transform);
|
||||
quotaInst.transform.localScale = new Vector3(1.3f, 1.3f, 1.3f);
|
||||
quotaInst.transform.localPosition = new Vector3(0.7f, -0.82f, 0);
|
||||
|
||||
var label = RoleGroupHidden[grouping.Key]
|
||||
var chanceText = quotaInst.transform.FindChild("Chance Text");
|
||||
chanceText.transform.localPosition = new Vector3(4.3f, 0.0993f, 0);
|
||||
|
||||
var countText = quotaInst.transform.FindChild("# Text");
|
||||
countText.transform.localPosition = new Vector3(1.9f, 0.0993f, 0f);
|
||||
|
||||
var blankLabel = quotaInst.transform.FindChild("BlankLabel").gameObject;
|
||||
var chanceLabel = quotaInst.transform.FindChild("Chance Label").gameObject;
|
||||
var countLabel = quotaInst.transform.FindChild("# Label").gameObject;
|
||||
blankLabel.Destroy();
|
||||
chanceLabel.Destroy();
|
||||
countLabel.Destroy();
|
||||
|
||||
categoryHeaderMasked.Background.sprite = MiraAssets.CategoryHeader.LoadAsset();
|
||||
categoryHeaderMasked.Background.sprite.texture.filterMode = FilterMode.Bilinear;
|
||||
categoryHeaderMasked.Background.sprite.texture.wrapMode = TextureWrapMode.Clamp;
|
||||
|
||||
categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.5f, -0.1833f, 0);
|
||||
|
||||
switch (name)
|
||||
{
|
||||
case StringNames.CrewmateRolesHeader:
|
||||
categoryHeaderMasked.Title.color = Palette.CrewmateRoleHeaderTextBlue;
|
||||
categoryHeaderMasked.Background.color = Palette.CrewmateRoleHeaderBlue;
|
||||
break;
|
||||
case StringNames.ImpostorRolesHeader:
|
||||
categoryHeaderMasked.Title.color = Palette.ImpostorRoleHeaderTextRed;
|
||||
categoryHeaderMasked.Background.color = Palette.ImpostorRoleHeaderRed;
|
||||
break;
|
||||
default:
|
||||
categoryHeaderMasked.Title.color = group.Color.Equals(MiraApiPlugin.DefaultHeaderColor) ? Color.white : group.Color.FindAlternateColor();
|
||||
categoryHeaderMasked.Divider.color = group.Color;
|
||||
categoryHeaderMasked.Background.color = group.Color;
|
||||
break;
|
||||
}
|
||||
|
||||
categoryHeaderMasked.Title.fontStyle = __instance.categoryHeaderEditRoleOrigin.Title.fontStyle;
|
||||
categoryHeaderMasked.Title.font = __instance.categoryHeaderEditRoleOrigin.Title.font;
|
||||
categoryHeaderMasked.Title.fontMaterial = __instance.categoryHeaderEditRoleOrigin.Title.fontMaterial;
|
||||
|
||||
categoryHeaderMasked.Divider.color = categoryHeaderMasked.Background.color;
|
||||
categoryHeaderMasked.Background.transform.localPosition = new Vector3(0.55f, -0.1833f, 0);
|
||||
categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y);
|
||||
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
|
||||
categoryHeaderMasked.transform.localPosition = new Vector3(-0.44f, ScrollerNum, -2f);
|
||||
categoryHeaderMasked.gameObject.SetActive(true);
|
||||
quotaInst.gameObject.SetActive(!RoleGroupHidden[group]);
|
||||
|
||||
var label = RoleGroupHidden[group]
|
||||
? "(Click to open)"
|
||||
: "(Click to close)";
|
||||
var newText = Object.Instantiate(categoryHeaderEditRole.Title, categoryHeaderEditRole.transform);
|
||||
newText.text = $"<size=50%>{label}</size>";
|
||||
newText.transform.localPosition = new Vector3(-3.3425f, -0.1706f, -1);
|
||||
var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform);
|
||||
newText.text = $"<size=70%>{label}</size>";
|
||||
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
|
||||
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
|
||||
if (!RoleGroupHidden[grouping.Key])
|
||||
Headers.Add(categoryHeaderMasked.gameObject);
|
||||
ScrollerNum -= 0.422f;
|
||||
|
||||
if (!RoleGroupHidden[group])
|
||||
{
|
||||
foreach (var role in grouping)
|
||||
{
|
||||
@@ -120,7 +182,7 @@ public static class RoleSettingMenuPatches
|
||||
continue;
|
||||
}
|
||||
|
||||
var option = CreateQuotaOption(__instance, roleBehaviour, ref num, num3);
|
||||
var option = CreateQuotaOption(__instance, roleBehaviour, num3);
|
||||
if (option is not null)
|
||||
{
|
||||
RoleOptionSettings.Add(option);
|
||||
@@ -129,23 +191,26 @@ public static class RoleSettingMenuPatches
|
||||
}
|
||||
}
|
||||
|
||||
var boxCol = categoryHeaderEditRole.gameObject.AddComponent<BoxCollider2D>();
|
||||
boxCol.size = new Vector2(3, 0.25f);
|
||||
boxCol.offset = new Vector2(-2.05f, -.175f);
|
||||
var boxCol = categoryHeaderMasked.gameObject.AddComponent<BoxCollider2D>();
|
||||
boxCol.size = new Vector2(7, 0.7f);
|
||||
boxCol.offset = new Vector2(1.5f, -0.3f);
|
||||
|
||||
var headerBtn = categoryHeaderEditRole.gameObject.AddComponent<PassiveButton>();
|
||||
var headerBtn = categoryHeaderMasked.gameObject.AddComponent<PassiveButton>();
|
||||
headerBtn.ClickSound = __instance.BackButton.GetComponent<PassiveButton>().ClickSound;
|
||||
headerBtn.OnMouseOver = new UnityEvent();
|
||||
headerBtn.OnMouseOut = new UnityEvent();
|
||||
headerBtn.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
RoleGroupHidden[grouping.Key] = !RoleGroupHidden[grouping.Key];
|
||||
foreach (var header in CategoryHeaderEditRoles)
|
||||
if (RoleGroupHidden.TryGetValue(group, out var value))
|
||||
{
|
||||
header.gameObject.Destroy();
|
||||
RoleGroupHidden[group] = !value;
|
||||
}
|
||||
CategoryHeaderEditRoles.Clear();
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
header.Destroy();
|
||||
}
|
||||
Headers.Clear();
|
||||
foreach (var option in RoleOptionSettings)
|
||||
{
|
||||
option.gameObject.Destroy();
|
||||
@@ -155,15 +220,20 @@ public static class RoleSettingMenuPatches
|
||||
}));
|
||||
headerBtn.SetButtonEnableState(true);
|
||||
|
||||
if (!RoleGroupHidden[grouping.Key])
|
||||
if (RoleGroupHidden.TryGetValue(group, out var value) && !value)
|
||||
{
|
||||
num -= 0.4f;
|
||||
ScrollerNum -= 0.4f;
|
||||
}
|
||||
}
|
||||
__instance.scrollBar.CalculateAndSetYBounds(__instance.roleChances.Count + 5, 1f, 6f, 0.43f);
|
||||
__instance.scrollBar.SetScrollBounds();
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void SetScrollBounds(this Scroller scroller)
|
||||
{
|
||||
scroller.CalculateAndSetYBounds(1 + 1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.Update))]
|
||||
public static bool UpdatePatch()
|
||||
@@ -175,7 +245,12 @@ public static class RoleSettingMenuPatches
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.OpenChancesTab))]
|
||||
public static void OpenChancesTabPostfix(RolesSettingsMenu __instance)
|
||||
{
|
||||
__instance.scrollBar.CalculateAndSetYBounds(__instance.roleChances.Count + 5, 1f, 6f, 0.43f);
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0 || !__instance.scrollBar)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.scrollBar.SetScrollBounds();
|
||||
__instance.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
@@ -223,31 +298,31 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
private static void CreateAdvancedSettings(RolesSettingsMenu __instance, RoleBehaviour role)
|
||||
{
|
||||
foreach (var option in __instance.advancedSettingChildren)
|
||||
foreach (var optBehaviour in __instance.AdvancedRolesSettings.GetComponentsInChildren<OptionBehaviour>())
|
||||
{
|
||||
Object.Destroy(option.gameObject);
|
||||
optBehaviour.gameObject.DestroyImmediate();
|
||||
}
|
||||
|
||||
__instance.advancedSettingChildren.Clear();
|
||||
|
||||
var num = -0.872f;
|
||||
var hasImage = role.RoleScreenshot != null;
|
||||
var num = hasImage ? -0.872f : -1;
|
||||
|
||||
var filteredOptions = GameSettingMenuPatches.SelectedMod?.Options.Where(x => x.AdvancedRole == role.GetType()) ?? [];
|
||||
// TODO: create sub groups under the role settings.
|
||||
var filteredOptions = GameSettingMenuPatches.SelectedMod?.OptionGroups
|
||||
.Where(x=> x.OptionableType == role.GetType())
|
||||
.SelectMany(x=>x.Options)
|
||||
.ToList() ?? [];
|
||||
|
||||
foreach (var option in filteredOptions)
|
||||
{
|
||||
if (option.AdvancedRole is not null && option.AdvancedRole != role.GetType())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var newOpt = option.CreateOption(
|
||||
__instance.checkboxOrigin,
|
||||
__instance.numberOptionOrigin,
|
||||
__instance.stringOptionOrigin,
|
||||
__instance.AdvancedRolesSettings.transform);
|
||||
|
||||
newOpt.transform.localPosition = new Vector3(2.17f, num, -2f);
|
||||
newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
|
||||
newOpt.SetClickMask(__instance.ButtonClickMask);
|
||||
|
||||
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
|
||||
@@ -285,14 +360,62 @@ public static class RoleSettingMenuPatches
|
||||
__instance.roleTitleText.text = TranslationController.Instance.GetString(
|
||||
role.StringName,
|
||||
new Il2CppReferenceArray<Il2CppSystem.Object>(0));
|
||||
__instance.roleScreenshot.sprite = Sprite.Create(
|
||||
customRole.Configuration.OptionsScreenshot.LoadAsset().texture,
|
||||
new Rect(0, 0, 370, 230),
|
||||
Vector2.one / 2,
|
||||
100);
|
||||
__instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
|
||||
__instance.roleHeaderSprite.color = customRole.RoleColor;
|
||||
__instance.roleHeaderText.color = customRole.RoleColor.GetAlternateColor();
|
||||
|
||||
var imgBg = __instance.AdvancedRolesSettings.transform.FindChild("Imagebackground");
|
||||
var labelBg = __instance.AdvancedRolesSettings.transform.FindChild("InfoLabelBackground");
|
||||
if (role.RoleScreenshot == null)
|
||||
{
|
||||
imgBg.gameObject.SetActive(false);
|
||||
__instance.roleScreenshot.gameObject.SetActive(false);
|
||||
__instance.roleDescriptionText.transform.parent.localPosition = new Vector3(1.5f, -0.2731f, -1);
|
||||
__instance.roleDescriptionText.transform.parent.localScale = new Vector3(0.09f, 0.2f, 0.5687f);
|
||||
labelBg.localPosition = new Vector3(-0.7f, 0.1054f, -2.5f);
|
||||
}
|
||||
else
|
||||
{
|
||||
imgBg.gameObject.SetActive(true);
|
||||
__instance.roleScreenshot.gameObject.SetActive(true);
|
||||
__instance.roleDescriptionText.transform.parent.localPosition = new Vector3(2.5176f, -0.2731f, -1f);
|
||||
__instance.roleDescriptionText.transform.parent.localScale = new Vector3(0.0675f, 0.1494f, 0.5687f);
|
||||
labelBg.transform.localPosition = new Vector3(1.082f, 0.1054f, -2.5f);
|
||||
|
||||
__instance.roleScreenshot.sprite = Sprite.Create(
|
||||
role.RoleScreenshot.texture,
|
||||
new Rect(0, 0, 370, 230),
|
||||
Vector2.one / 2,
|
||||
100);
|
||||
__instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
|
||||
}
|
||||
|
||||
__instance.roleHeaderSprite.color = customRole.OptionsMenuColor;
|
||||
__instance.roleHeaderText.color = customRole.OptionsMenuColor.FindAlternateColor();
|
||||
|
||||
var categoryHeaderMasked = __instance.AdvancedRolesSettings.transform.Find("CategoryHeaderMasked").GetComponent<CategoryHeaderMasked>();
|
||||
|
||||
if (categoryHeaderMasked.Title.gameObject.TryGetComponent<TextTranslatorTMP>(out var comp))
|
||||
{
|
||||
comp.Destroy();
|
||||
}
|
||||
|
||||
categoryHeaderMasked.Title.text = "RETURN TO ROLE SETTINGS";
|
||||
|
||||
if (!categoryHeaderMasked.gameObject.TryGetComponent<PassiveButton>(out _))
|
||||
{
|
||||
var boxCol = categoryHeaderMasked.gameObject.AddComponent<BoxCollider2D>();
|
||||
boxCol.size = new Vector2(7, 0.7f);
|
||||
boxCol.offset = new Vector2(1.5f, -0.3f);
|
||||
|
||||
var headerBtn = categoryHeaderMasked.gameObject.AddComponent<PassiveButton>();
|
||||
headerBtn.ClickSound = __instance.BackButton.GetComponent<PassiveButton>().ClickSound;
|
||||
headerBtn.OnMouseOver = new UnityEvent();
|
||||
headerBtn.OnMouseOut = new UnityEvent();
|
||||
headerBtn.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.OpenChancesTab();
|
||||
}));
|
||||
headerBtn.SetButtonEnableState(true);
|
||||
}
|
||||
|
||||
var bg = __instance.AdvancedRolesSettings.transform.Find("Background");
|
||||
bg.localPosition = new Vector3(1.4041f, -7.08f, 0);
|
||||
@@ -319,7 +442,7 @@ public static class RoleSettingMenuPatches
|
||||
__instance.RefreshChildren();
|
||||
}
|
||||
|
||||
private static RoleOptionSetting? CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index)
|
||||
private static RoleOptionSetting? CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, int index)
|
||||
{
|
||||
if (role is not ICustomRole customRole)
|
||||
{
|
||||
@@ -327,26 +450,31 @@ public static class RoleSettingMenuPatches
|
||||
return null;
|
||||
}
|
||||
|
||||
if (customRole.Configuration.HideSettings)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var roleOptionSetting = Object.Instantiate(
|
||||
__instance.roleOptionSettingOrigin,
|
||||
Vector3.zero,
|
||||
Quaternion.identity,
|
||||
__instance.RoleChancesSettings.transform);
|
||||
roleOptionSetting.transform.localPosition = new Vector3(-0.15f, yPos, -2f);
|
||||
roleOptionSetting.transform.localPosition = new Vector3(-0.1f, ScrollerNum, -2f);
|
||||
|
||||
roleOptionSetting.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, role, 20);
|
||||
roleOptionSetting.labelSprite.color = customRole.RoleColor;
|
||||
roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor;
|
||||
roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged);
|
||||
roleOptionSetting.SetClickMask(__instance.ButtonClickMask);
|
||||
__instance.roleChances.Add(roleOptionSetting);
|
||||
|
||||
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
|
||||
roleOptionSetting.titleText.color = customRole.RoleColor.GetAlternateColor();
|
||||
roleOptionSetting.titleText.color = customRole.OptionsMenuColor.FindAlternateColor();
|
||||
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
||||
|
||||
if (GameSettingMenuPatches.SelectedMod is null ||
|
||||
GameSettingMenuPatches.SelectedMod.Options.Exists(
|
||||
x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role)))
|
||||
GameSettingMenuPatches.SelectedMod.OptionGroups
|
||||
.Exists(x => x.OptionableType == role.GetType()))
|
||||
{
|
||||
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
|
||||
newButton.name = "ConfigButton";
|
||||
@@ -359,7 +487,7 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
var passiveButton = newButton.GetComponent<GameOptionButton>();
|
||||
passiveButton.OnClick = new ButtonClickedEvent();
|
||||
passiveButton.interactableColor = btnRend.color = customRole.RoleColor.GetAlternateColor();
|
||||
passiveButton.interactableColor = btnRend.color = customRole.OptionsMenuColor.FindAlternateColor();
|
||||
passiveButton.interactableHoveredColor = Color.white;
|
||||
|
||||
passiveButton.OnClick.AddListener((UnityAction)(() => { ChangeTab(role, __instance); }));
|
||||
@@ -379,7 +507,7 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1)
|
||||
{
|
||||
yPos += -0.43f;
|
||||
ScrollerNum -= 0.43f;
|
||||
}
|
||||
|
||||
return roleOptionSetting;
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Voting;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
@@ -14,30 +15,30 @@ namespace MiraAPI.Patches;
|
||||
/// General patches for the PlayerControl class.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(PlayerControl))]
|
||||
public static class PlayerControlPatches
|
||||
internal static class PlayerControlPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the modifier component to the player on start.
|
||||
/// </summary>
|
||||
/// <param name="__instance">PlayerControl instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerControl.Start))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerControlStartPostfix(PlayerControl __instance)
|
||||
{
|
||||
if (__instance.gameObject.TryGetComponent<ModifierComponent>(out var comp))
|
||||
if (__instance.gameObject.TryGetComponent<ModifierComponent>(out var modifierComp))
|
||||
{
|
||||
comp.DestroyImmediate();
|
||||
modifierComp.DestroyImmediate();
|
||||
}
|
||||
|
||||
if (__instance.gameObject.TryGetComponent<PlayerVoteData>(out var voteComp))
|
||||
{
|
||||
voteComp.DestroyImmediate();
|
||||
}
|
||||
|
||||
__instance.gameObject.AddComponent<ModifierComponent>();
|
||||
__instance.gameObject.AddComponent<PlayerVoteData>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the OnDeath method for all active modifiers.
|
||||
/// </summary>
|
||||
/// <param name="__instance">PlayerControl instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerControl.Die))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerControlDiePostfix(PlayerControl __instance, DeathReason reason)
|
||||
{
|
||||
var deathEvent = new PlayerDeathEvent(__instance, reason);
|
||||
@@ -51,13 +52,9 @@ public static class PlayerControlPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to trigger the <see cref="CompleteTaskEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="__instance">PlayerControl instance.</param>
|
||||
/// <param name="idx">The task id.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerControl.CompleteTask))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerCompleteTaskPostfix(PlayerControl __instance, uint idx)
|
||||
{
|
||||
var playerTask = __instance.myTasks.ToArray().First(playerTask => playerTask.Id == idx);
|
||||
@@ -68,14 +65,9 @@ public static class PlayerControlPatches
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to trigger the <see cref="BeforeMurderEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The source player.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
/// <param name="didSucceed">Whether the kill succeeded.</param>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed)
|
||||
{
|
||||
var beforeMurderEvent = new BeforeMurderEvent(__instance, target);
|
||||
@@ -84,12 +76,9 @@ public static class PlayerControlPatches
|
||||
didSucceed = beforeMurderEvent.IsCancelled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FixedUpdate handler for custom roles and custom buttons.
|
||||
/// </summary>
|
||||
/// <param name="__instance">PlayerControl instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
|
||||
{
|
||||
if (__instance.Data?.Role is ICustomRole customRole)
|
||||
@@ -117,15 +106,4 @@ public static class PlayerControlPatches
|
||||
button.FixedUpdateHandler(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clear from modifier component cache.
|
||||
/// </summary>
|
||||
/// <param name="__instance">PlayerControl instance.</param>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerControl.OnDestroy))]
|
||||
public static void PlayerControlOnDestroyPrefix(PlayerControl __instance)
|
||||
{
|
||||
Utilities.Extensions.ModifierComponents.Remove(__instance);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using HarmonyLib;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Events.Vanilla.Meeting;
|
||||
using MiraAPI.Roles;
|
||||
|
||||
@@ -9,7 +12,7 @@ namespace MiraAPI.Patches.Roles;
|
||||
/// Patches for custom ejection messages.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(ExileController))]
|
||||
public static class EjectionPatches
|
||||
internal static class EjectionPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(ExileController.Begin))]
|
||||
@@ -31,4 +34,14 @@ public static class EjectionPatches
|
||||
|
||||
__instance.completeString = role.GetCustomEjectionMessage(__instance.initData.networkedPlayer);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))]
|
||||
public static class WrapUpPatch
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
var @event = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
@@ -21,6 +22,14 @@ public static class GameStartupPatch
|
||||
}
|
||||
|
||||
_runOnce = true;
|
||||
|
||||
if (MiraPluginManager.Instance.QueuedRoleRegistrations.Count <= 0) return;
|
||||
foreach (var queue in MiraPluginManager.Instance.QueuedRoleRegistrations)
|
||||
{
|
||||
CustomRoleManager.RegisterRoleTypes(queue.Value, queue.Key);
|
||||
}
|
||||
|
||||
MiraPluginManager.Instance.QueuedRoleRegistrations.Clear();
|
||||
CustomRoleManager.RegisterInRoleManager();
|
||||
}
|
||||
}
|
||||
@@ -22,4 +22,4 @@ public static class RoleBehaviourPatch
|
||||
__result = behaviour.RoleColor;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,62 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(RoleManager))]
|
||||
public static class RoleManagerPatches
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleManager.SetRole))]
|
||||
public static void SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
|
||||
public static bool SetRolePatch(RoleManager __instance, PlayerControl targetPlayer, RoleTypes roleType)
|
||||
{
|
||||
if (!targetPlayer)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var data = targetPlayer.Data;
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogError("It shouldn't be possible, but " + targetPlayer.name + " still doesn't have PlayerData during role selection.");
|
||||
return false;
|
||||
}
|
||||
if (data.Role)
|
||||
{
|
||||
data.Role.Deinitialize(targetPlayer);
|
||||
Object.Destroy(data.Role.gameObject);
|
||||
}
|
||||
var roleBehaviour = Object.Instantiate<RoleBehaviour>(__instance.AllRoles.First(r => r.Role == roleType), data.gameObject.transform);
|
||||
roleBehaviour.Initialize(targetPlayer);
|
||||
targetPlayer.Data.Role = roleBehaviour;
|
||||
targetPlayer.Data.RoleType = roleType;
|
||||
|
||||
// basically everything is the same except this one if statement
|
||||
// innersloth decided to check the roleType manually instead of the role behaviour.
|
||||
if (!roleBehaviour.IsDead)
|
||||
{
|
||||
targetPlayer.Data.RoleWhenAlive = new Il2CppSystem.Nullable<RoleTypes>(roleType);
|
||||
}
|
||||
roleBehaviour.AdjustTasks(targetPlayer);
|
||||
switch (roleBehaviour.IsDead)
|
||||
{
|
||||
case true when !targetPlayer.Data.IsDead:
|
||||
targetPlayer.Die(DeathReason.Kill, false);
|
||||
return false;
|
||||
case false when targetPlayer.Data.IsDead:
|
||||
targetPlayer.Revive();
|
||||
break;
|
||||
}
|
||||
|
||||
var @event = new SetRoleEvent(targetPlayer, roleType);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace MiraAPI.Patches.Roles;
|
||||
/// <summary>
|
||||
/// Patches to return the correct role counts.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(RoleOptionsCollectionV08))]
|
||||
[HarmonyPatch(typeof(RoleOptionsCollectionV09))]
|
||||
public static class RoleOptionsCollectionPatch
|
||||
{
|
||||
/// <summary>
|
||||
@@ -16,7 +16,7 @@ public static class RoleOptionsCollectionPatch
|
||||
/// </summary>
|
||||
/// <returns>Return false to skip original method, true to not.</returns>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetChancePerGame))]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetChancePerGame))]
|
||||
public static bool GetChancePrefix(RoleTypes role, ref int __result)
|
||||
{
|
||||
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
|
||||
@@ -46,7 +46,7 @@ public static class RoleOptionsCollectionPatch
|
||||
/// </summary>
|
||||
/// <returns>Return false to skip original method, true to not.</returns>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetNumPerGame))]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetNumPerGame))]
|
||||
public static bool GetNumPrefix(RoleTypes role, ref int __result)
|
||||
{
|
||||
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
|
||||
|
||||
Loaded 100 of 125 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user