mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 11:39:45 +00:00
Compare commits
283
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
834d1d878e | ||
|
|
790b37e8d3 | ||
|
|
dfa8b29710 | ||
|
|
2c0042e6a8 | ||
|
|
70f63f8b16 | ||
|
|
03a3733966 | ||
|
|
4b7fa906b9 | ||
|
|
b7173d18ab | ||
|
|
ffc7a1a4fd | ||
|
|
a1611f5f7a | ||
|
|
1664aa1003 | ||
|
|
94197e38dd | ||
|
|
97a0b2084a | ||
|
|
1d87790212 | ||
|
|
d8afc6887d | ||
|
|
c0a406610d | ||
|
|
d73c21936c | ||
|
|
449192f0ef | ||
|
|
f3614b9a5c | ||
|
|
a62c6d496c | ||
|
|
bebd648691 | ||
|
|
ecadfa86dd | ||
|
|
5ad53f695f | ||
|
|
31a9c0c98d | ||
|
|
ea389b986f | ||
|
|
ef85fab6a6 | ||
|
|
b2fdaaf89f | ||
|
|
e6043b6f5f | ||
|
|
ffe0db0e9e | ||
|
|
705ef64b4d | ||
|
|
ebdb4a09ca | ||
|
|
ac113b6a7f | ||
|
|
1f6962efe9 | ||
|
|
6b37b9c09c | ||
|
|
f515516937 | ||
|
|
8b2d24ccc1 | ||
|
|
57bab258e5 | ||
|
|
04d5e72caa | ||
|
|
b4e6e448f2 | ||
|
|
b19a0178ad | ||
|
|
8a71f13874 | ||
|
|
809e7a07ab | ||
|
|
47394c6afe | ||
|
|
a2a1e91439 | ||
|
|
2f5ca32475 | ||
|
|
5fb59408fa | ||
|
|
57c842ece6 | ||
|
|
63e552245d | ||
|
|
43d7f80660 | ||
|
|
45291b0e98 | ||
|
|
45972f71a9 | ||
|
|
a6f7171b71 | ||
|
|
e6d3819517 | ||
|
|
b97ada22bc | ||
|
|
5f67960b28 | ||
|
|
b4e30b2b68 | ||
|
|
2237d3a2df | ||
|
|
e708c23f81 | ||
|
|
3ca33b1d86 | ||
|
|
8967348cd3 | ||
|
|
305d2c3714 | ||
|
|
85a0275eea | ||
|
|
f006deab3f | ||
|
|
a274f23e65 | ||
|
|
f1a5aef581 | ||
|
|
ce15e93e59 | ||
|
|
08ca995eb1 | ||
|
|
453259df98 | ||
|
|
2ec3eec370 | ||
|
|
2ee8730ba4 | ||
|
|
c89e5938cc | ||
|
|
513be01c02 | ||
|
|
01fa1f0915 | ||
|
|
355eef509b | ||
|
|
763a493283 | ||
|
|
68e421bf91 | ||
|
|
c343ab5e9f | ||
|
|
9c1ef70ea7 | ||
|
|
c0c8fa08ff | ||
|
|
e7fba386aa | ||
|
|
72e5e0c137 | ||
|
|
4422072060 | ||
|
|
8145c8e599 | ||
|
|
afd6fbc3b6 | ||
|
|
74741b41a7 | ||
|
|
f98429b5f9 | ||
|
|
bf7edb30ab | ||
|
|
b4527014b7 | ||
|
|
277391ef1c | ||
|
|
dfe124d67a | ||
|
|
2b06633488 | ||
|
|
4784b1f0ad | ||
|
|
d9d0d6bc62 | ||
|
|
6b3038f397 | ||
|
|
4e1db039a0 | ||
|
|
62bee24789 | ||
|
|
2156c80467 | ||
|
|
0ff60071c2 | ||
|
|
de2f3f0d95 | ||
|
|
05f132c506 | ||
|
|
ab54c44434 | ||
|
|
bf652b68c3 | ||
|
|
00377c7926 | ||
|
|
55555604f3 | ||
|
|
de77a9fa92 | ||
|
|
eba2a037d3 | ||
|
|
bc7f772de9 | ||
|
|
f43191c4f2 | ||
|
|
cd818a18cf | ||
|
|
56d4e399d9 | ||
|
|
0d41ebbc13 | ||
|
|
9388ff487a | ||
|
|
720491e5cf | ||
|
|
9884df356e | ||
|
|
7ec4addacc | ||
|
|
bc9bc0ce24 | ||
|
|
aff1c311fa | ||
|
|
0d2a9d9700 | ||
|
|
669c541279 | ||
|
|
2e5f9639b3 | ||
|
|
e6d1f440c8 | ||
|
|
2ac8bdc240 | ||
|
|
d5d99001ea | ||
|
|
5628b79814 | ||
|
|
72a44f65f1 | ||
|
|
db6a336a63 | ||
|
|
90b23ec2ca | ||
|
|
20e927208c | ||
|
|
13ef3bdd49 | ||
|
|
b3ff6755bb | ||
|
|
67604600cd | ||
|
|
6e35de6185 | ||
|
|
a26ec51c7d | ||
|
|
3f6ae4cc41 | ||
|
|
15a2cdcd5b | ||
|
|
cf0bd6ec38 | ||
|
|
130236e899 | ||
|
|
3b7b247ac0 | ||
|
|
52de9a22de | ||
|
|
372a9e3aa9 | ||
|
|
10a9dcfa34 | ||
|
|
d46fdb0185 | ||
|
|
a361afbcbf | ||
|
|
24cdac0243 | ||
|
|
5fe75843ad | ||
|
|
a0436dcbba | ||
|
|
35be381a77 | ||
|
|
1298beb517 | ||
|
|
d41c19a176 | ||
|
|
476f4400ad | ||
|
|
2b46d90570 | ||
|
|
ae4aaf7af9 | ||
|
|
757b9436ca | ||
|
|
7ea10afffb | ||
|
|
0ab80c42f9 | ||
|
|
bc144568f4 | ||
|
|
07f402e659 | ||
|
|
1a6aed539a | ||
|
|
023cdaa470 | ||
|
|
98dd5ca056 | ||
|
|
1f3e21ce19 | ||
|
|
15e441dd71 | ||
|
|
8c67bbbd44 | ||
|
|
aa584a8f44 | ||
|
|
a8a584b61b | ||
|
|
acc4b1c8dd | ||
|
|
f9600bc906 | ||
|
|
f3da06870f | ||
|
|
9180dec9e1 | ||
|
|
ef6dedbd55 | ||
|
|
5d3c3ed319 | ||
|
|
5b22d667fd | ||
|
|
fb894c9f2c | ||
|
|
d0449f57b5 | ||
|
|
5ffd591258 | ||
|
|
cc0ac1b988 | ||
|
|
00b96f6e61 | ||
|
|
6cb7f9f145 | ||
|
|
b94472ee07 | ||
|
|
f8056c29da | ||
|
|
e7f9a3bb60 | ||
|
|
8d4f2baf1e | ||
|
|
11ba3dae64 | ||
|
|
86145f94f4 | ||
|
|
a9ded0b313 | ||
|
|
e0da386567 | ||
|
|
6f1da2de9f | ||
|
|
b85358e304 | ||
|
|
2c901bb9b9 | ||
|
|
68f40a96b7 | ||
|
|
a96ffc42f2 | ||
|
|
0907b14893 | ||
|
|
7a7204b0e0 | ||
|
|
e57e88c179 | ||
|
|
878eab25b8 | ||
|
|
e4f15f2ed9 | ||
|
|
bdc9fa28ac | ||
|
|
64604aff98 | ||
|
|
32a44d646d | ||
|
|
8f590b94a8 | ||
|
|
3ba71d8f19 | ||
|
|
96a47d480a | ||
|
|
bb72d885cf | ||
|
|
e242975d75 | ||
|
|
be6bc07a62 | ||
|
|
a6d55ec90f | ||
|
|
e5b6400eb8 | ||
|
|
0d1d1891a8 | ||
|
|
54d85a6996 | ||
|
|
531a799a24 | ||
|
|
3e6eb49d93 | ||
|
|
3fabe85a18 | ||
|
|
a3fc847d07 | ||
|
|
ddfad0752d | ||
|
|
f1846375ca | ||
|
|
fcb693e899 | ||
|
|
21bec074fa | ||
|
|
30516104aa | ||
|
|
e7508e299b | ||
|
|
b0cd1d30b3 | ||
|
|
9f3a40b48f | ||
|
|
31ffe6274c | ||
|
|
91c9a27df8 | ||
|
|
7484dd1a9f | ||
|
|
9c9964eecf | ||
|
|
78fad65b97 | ||
|
|
aef4d6daf1 | ||
|
|
87b94b1b6d | ||
|
|
cf92b97e8b | ||
|
|
bd0aa68cb8 | ||
|
|
f43a542134 | ||
|
|
efd176f7a4 | ||
|
|
b2fabdb139 | ||
|
|
a651099217 | ||
|
|
e2f39c29a8 | ||
|
|
140b697aaf | ||
|
|
a56b6d52ef | ||
|
|
3a36acd131 | ||
|
|
3adb7ef018 | ||
|
|
8c763e2a57 | ||
|
|
2df50112eb | ||
|
|
ce330325e4 | ||
|
|
516cc83170 | ||
|
|
89cd32d4a4 | ||
|
|
f0e37daf0e | ||
|
|
71e2f2e375 | ||
|
|
a5ddec48c8 | ||
|
|
9e6524817a | ||
|
|
5f4461f7e5 | ||
|
|
5e2ca191ba | ||
|
|
5af10b52a4 | ||
|
|
a0d3afc7c7 | ||
|
|
2e769d79f2 | ||
|
|
41305ad0e2 | ||
|
|
a8ac00d270 | ||
|
|
3cd7acbc70 | ||
|
|
ce583d11de | ||
|
|
31132e6e65 | ||
|
|
1a53e2b045 | ||
|
|
2418e17af6 | ||
|
|
6fb0afe14e | ||
|
|
ab0063dd99 | ||
|
|
a35bf60870 | ||
|
|
692b9d7180 | ||
|
|
b99efaa420 | ||
|
|
2ecfa267d6 | ||
|
|
9afe821b0f | ||
|
|
a8d076656d | ||
|
|
be46ee7329 | ||
|
|
75a15dc090 | ||
|
|
cffb104392 | ||
|
|
429b64fc03 | ||
|
|
bf5dadc6a0 | ||
|
|
b2137230da | ||
|
|
9e8c05f21d | ||
|
|
2f56c95b39 | ||
|
|
38f72c42a3 | ||
|
|
5b608f5b3a | ||
|
|
2248726450 | ||
|
|
c60537cbd1 | ||
|
|
f5b4bdd1d4 | ||
|
|
cd6c4f5390 | ||
|
|
5cc29d903a |
No files matched your search
+1
-2
@@ -1,2 +1 @@
|
||||
[*.{cs,vb}]
|
||||
dotnet_diagnostic.CA1707.severity = none
|
||||
[*.{cs,vb}]
|
||||
@@ -3,6 +3,7 @@
|
||||
.vs/
|
||||
obj/
|
||||
bin/
|
||||
.qodo/
|
||||
|
||||
*.user
|
||||
*.lock.json
|
||||
+4
-4
@@ -1,13 +1,13 @@
|
||||
<Project>
|
||||
<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="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" Private="false" ExcludeAssets="runtime;native" />
|
||||
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.9.9" 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)' != ''">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" UseSymboliclinksIfPossible="true" />
|
||||
</Target>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.2.0</VersionPrefix>
|
||||
<VersionPrefix>0.2.1</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.Keybinds;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
@@ -15,10 +16,10 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
||||
public override string Name => "Freeze";
|
||||
|
||||
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
|
||||
|
||||
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
|
||||
public override bool PauseTimerInVent => true;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override MiraKeybind? Keybind => MiraGlobalKeybinds.PrimaryAbility;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using MiraAPI.Example.Modifiers;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
@@ -15,6 +16,7 @@ public class MeetingButton : CustomActionButton
|
||||
public override int MaxUses => 3;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override MiraKeybind? Keybind => MiraGlobalKeybinds.ModifierPrimaryAbility;
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -12,6 +13,8 @@ public class NeutralKillerButton : CustomActionButton
|
||||
public override string Name => "Win Game";
|
||||
public override float Cooldown => 0f;
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override MiraKeybind Keybind => ExampleKeybinds.NeutralWinKeybind;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||
|
||||
@@ -3,6 +3,7 @@ using MiraAPI.Example.Options.Roles;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
@@ -21,6 +22,7 @@ public class TeleportButton : CustomActionButton
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
||||
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
||||
public static bool IsZoom { get; private set; }
|
||||
public override MiraKeybind? Keybind => MiraGlobalKeybinds.PrimaryAbility;
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
|
||||
public class ThinkButton : CustomActionButton
|
||||
{
|
||||
public override string Name => "Think";
|
||||
public override float Cooldown => 15f;
|
||||
public override float EffectDuration => 10f;
|
||||
public override int MaxUses => 1;
|
||||
public override ButtonUsesMode UsesMode => ButtonUsesMode.PerRound;
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
protected override void OnClick()
|
||||
{
|
||||
// This button does absolutely nothing besides changing its text
|
||||
Button!.OverrideText("Thinking...");
|
||||
}
|
||||
|
||||
public override void OnEffectEnd()
|
||||
{
|
||||
Button!.OverrideText("Think");
|
||||
}
|
||||
|
||||
public override bool IsEffectCancellable()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
return role != null && role.IsImpostor;
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ namespace MiraAPI.Example;
|
||||
public static class ExampleAssets
|
||||
{
|
||||
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png");
|
||||
public static LoadableResourceAsset CallMeetingButton { get; } = new("MiraAPI.Example.Resources.CallMeeting.png");
|
||||
|
||||
// Credit to EpicHorrors for the teleport button asset.
|
||||
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
|
||||
|
||||
@@ -6,7 +6,10 @@ namespace MiraAPI.Example;
|
||||
[RegisterCustomColors]
|
||||
public static class ExampleColors
|
||||
{
|
||||
public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f));
|
||||
public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f))
|
||||
{
|
||||
ColorBrightness = CustomColorBrightness.Lighter,
|
||||
};
|
||||
|
||||
public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f));
|
||||
|
||||
|
||||
@@ -9,10 +9,7 @@ using MiraAPI.Events.Vanilla.Player;
|
||||
using MiraAPI.Example.Buttons.Freezer;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
|
||||
@@ -21,7 +18,7 @@ public static class ExampleEventHandlers
|
||||
public static void Initialize()
|
||||
{
|
||||
// You can register event handlers with the MiraEventManager class.
|
||||
MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||
var handle = MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
|
||||
});
|
||||
@@ -35,6 +32,9 @@ public static class ExampleEventHandlers
|
||||
{
|
||||
Logger<ExamplePlugin>.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType.ToString()}");
|
||||
});
|
||||
|
||||
// You can also unregister event handlers using the handle returned from RegisterEventHandler.
|
||||
MiraEventManager.UnregisterEventHandler(handle);
|
||||
}
|
||||
|
||||
// If you want to add extra votes to a player, do something like this.
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
using MiraAPI.Keybinds;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
|
||||
[RegisterCustomKeybinds]
|
||||
public static class ExampleKeybinds
|
||||
{
|
||||
public static MiraKeybind NeutralWinKeybind { get; } = new("Neutral Win", KeyboardKeyCode.C, [ModifierKey.Shift]);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.LocalSettings.Attributes;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example;
|
||||
|
||||
public class ExampleLocalSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
{
|
||||
public override string TabName => "Mira Example";
|
||||
protected override bool ShouldCreateLabels => false;
|
||||
|
||||
public override LocalSettingTabAppearance TabAppearance => new()
|
||||
{
|
||||
ToggleActiveColor = Color.blue,
|
||||
ToggleInactiveColor = Color.red,
|
||||
};
|
||||
|
||||
[LocalSettingsButton]
|
||||
public LocalSettingsButton ExampleButton { get; private set; } = new("Example Button", OnExampleButtonClick);
|
||||
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> ExampleToggle { get; private set; } = config.Bind("General", "Example Bool", true);
|
||||
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> ExampleToggle2 { get; private set; } = config.Bind("General", "Example Bool 2", false);
|
||||
|
||||
[LocalSliderSetting(suffixType: MiraNumberSuffixes.Percent, formatString: "0", roundValue: true)]
|
||||
public ConfigEntry<float> ExampleSlider { get; private set; } = config.Bind("General", "Slider", 50f);
|
||||
|
||||
[LocalNumberSetting]
|
||||
public ConfigEntry<float> ExampleNumber { get; private set; } = config.Bind("General", "Example Number", 4f);
|
||||
|
||||
[LocalNumberSetting(min: 0.1f, max: 2.5f, increment: 0.1f, formatString: "0.0", suffixType: MiraNumberSuffixes.Multiplier)]
|
||||
public ConfigEntry<float> ExampleFloatNumber { get; private set; } = config.Bind("General", "Example Multplier", 1f);
|
||||
|
||||
[LocalEnumSetting]
|
||||
public ConfigEntry<ExampleEnumSetting> ExampleEnum { get; private set; } = config.Bind("General", "Example Enum", ExampleEnumSetting.Fries);
|
||||
|
||||
private static void OnExampleButtonClick()
|
||||
{
|
||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.Open();
|
||||
}
|
||||
}
|
||||
|
||||
public enum ExampleEnumSetting
|
||||
{
|
||||
Cheeseburger,
|
||||
Sandwich,
|
||||
Fries,
|
||||
ChickenNuggets,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using BepInEx;
|
||||
using System.Linq;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
|
||||
@@ -9,11 +9,9 @@
|
||||
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\ExampleButton.png" />
|
||||
<EmbeddedResource Include="Resources\FortniteBanner.jpeg" />
|
||||
<EmbeddedResource Include="Resources\TeleportButton.png" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\AmongUs.props" />
|
||||
</Project>
|
||||
@@ -1,12 +1,26 @@
|
||||
using MiraAPI.Example.Options.Modifiers;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers;
|
||||
|
||||
public class CaptainModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "Captain";
|
||||
public override LoadableAsset<Sprite>? ModifierIcon => ExampleAssets.CallMeetingButton;
|
||||
|
||||
public override void OnDeath(DeathReason reason)
|
||||
{
|
||||
Player.RemoveModifier(this);
|
||||
}
|
||||
|
||||
public override string GetDescription()
|
||||
{
|
||||
return $"You can call a meeting from anywhere on the map.";
|
||||
}
|
||||
|
||||
public override int GetAssignmentChance()
|
||||
{
|
||||
|
||||
@@ -6,6 +6,11 @@ public class HighPriorityModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "High Priority";
|
||||
|
||||
public override string GetDescription()
|
||||
{
|
||||
return "You are high priority! Idrk what this means either tbh...";
|
||||
}
|
||||
|
||||
public override int GetAssignmentChance()
|
||||
{
|
||||
return 100;
|
||||
|
||||
@@ -10,6 +10,8 @@ public class ExampleOptions2 : AbstractOptionGroup
|
||||
|
||||
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
|
||||
|
||||
public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false);
|
||||
public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever");
|
||||
public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false);
|
||||
|
||||
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 36 KiB |
@@ -1,5 +1,7 @@
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Patches.Stubs;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -13,37 +15,64 @@ public class ChameloenRole : CrewmateRole, ICustomRole
|
||||
public Color RoleColor => Palette.AcceptedGreen;
|
||||
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
|
||||
|
||||
private bool _shouldHide;
|
||||
|
||||
public CustomRoleConfiguration Configuration => new(this)
|
||||
{
|
||||
OptionsScreenshot = ExampleAssets.Banner,
|
||||
IntroSound = CustomRoleUtils.GetIntroSound(RoleTypes.Shapeshifter),
|
||||
};
|
||||
|
||||
public void PlayerControlFixedUpdate(PlayerControl playerControl)
|
||||
public override void Initialize(PlayerControl player)
|
||||
{
|
||||
if (playerControl.MyPhysics.Velocity.magnitude > 0)
|
||||
Logger<ExamplePlugin>.Info("Initializing ChamelonRole for player: " + player.PlayerId);
|
||||
RoleBehaviourStubs.Initialize(this, player);
|
||||
_shouldHide = true;
|
||||
}
|
||||
|
||||
public void FixedUpdate()
|
||||
{
|
||||
if (!Player || !_shouldHide)
|
||||
{
|
||||
SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite;
|
||||
TextMeshPro tmp = playerControl.cosmetics.nameText;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Player.MyPhysics.Velocity.magnitude > 0)
|
||||
{
|
||||
var rend = Player.cosmetics.currentBodySprite.BodySprite;
|
||||
var tmp = Player.cosmetics.nameText;
|
||||
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, 1), Time.deltaTime * 4f);
|
||||
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f);
|
||||
|
||||
foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
{
|
||||
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, 1), Time.deltaTime * 4f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SpriteRenderer rend = playerControl.cosmetics.currentBodySprite.BodySprite;
|
||||
TextMeshPro tmp = playerControl.cosmetics.nameText;
|
||||
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
SpriteRenderer rend = Player.cosmetics.currentBodySprite.BodySprite;
|
||||
TextMeshPro tmp = Player.cosmetics.nameText;
|
||||
tmp.color = Color.Lerp(tmp.color, new Color(tmp.color.r, tmp.color.g, tmp.color.b, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
rend.color = Color.Lerp(rend.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
|
||||
foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
|
||||
{
|
||||
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, Player.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Deinitialize(PlayerControl targetPlayer)
|
||||
{
|
||||
Logger<ExamplePlugin>.Info("Deinitializing ChamelonRole for player: " + targetPlayer.PlayerId);
|
||||
RoleBehaviourStubs.Deinitialize(this, targetPlayer);
|
||||
_shouldHide = false;
|
||||
foreach (var cosmetic in Player.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>(true))
|
||||
{
|
||||
cosmetic.color = Color.white;
|
||||
}
|
||||
Player.cosmetics.currentBodySprite.BodySprite.color = Color.white;
|
||||
Player.cosmetics.nameText.color = Color.white;
|
||||
}
|
||||
}
|
||||
@@ -13,19 +13,24 @@ namespace MiraAPI.Colors;
|
||||
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the main color.
|
||||
/// Gets or sets the main color.
|
||||
/// </summary>
|
||||
public Color32 MainColor { get; } = mainColor;
|
||||
public Color32 MainColor { get; set; } = mainColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shadow color.
|
||||
/// Gets or sets the shadow color.
|
||||
/// </summary>
|
||||
public Color32 ShadowColor { get; } = shadowColor;
|
||||
public Color32 ShadowColor { get; set; } = shadowColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the color.
|
||||
/// Gets or sets the name of the color.
|
||||
/// </summary>
|
||||
public StringNames Name { get; } = name;
|
||||
public StringNames Name { get; set; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the color is lighter or darker. Can be used to help with colorblind stuff.
|
||||
/// </summary>
|
||||
public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
@@ -42,3 +47,19 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to help with colorblind stuff.
|
||||
/// </summary>
|
||||
public enum CustomColorBrightness
|
||||
{
|
||||
/// <summary>
|
||||
/// A lighter color.
|
||||
/// </summary>
|
||||
Lighter,
|
||||
|
||||
/// <summary>
|
||||
/// A darker color.
|
||||
/// </summary>
|
||||
Darker,
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Event handle to use when unregistering an event.
|
||||
/// </summary>
|
||||
public class MiraEventHandle
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the type of event.
|
||||
/// </summary>
|
||||
public Type EventType { get; }
|
||||
|
||||
internal MiraEventWrapper EventWrapper { get; }
|
||||
|
||||
internal MiraEventHandle(Type type, MiraEventWrapper eventWrapper)
|
||||
{
|
||||
EventType = type;
|
||||
EventWrapper = eventWrapper;
|
||||
}
|
||||
}
|
||||
@@ -21,14 +21,21 @@ 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;
|
||||
}
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
((Action<T>)handler.EventHandler).Invoke(eventInstance);
|
||||
try
|
||||
{
|
||||
((Action<T>)handler.EventHandler).Invoke(eventInstance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -43,14 +50,21 @@ 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;
|
||||
}
|
||||
|
||||
foreach (var handler in handlers)
|
||||
{
|
||||
handler.EventHandler.DynamicInvoke(eventInstance);
|
||||
try
|
||||
{
|
||||
handler.EventHandler.DynamicInvoke(eventInstance);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -61,47 +75,78 @@ public static class MiraEventManager
|
||||
/// </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. Higher values are called first.</param>
|
||||
public static void RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
|
||||
/// <param name="priority">The priority of the event handler. Lower values are called first.</param>
|
||||
/// <returns>An event handle to use when unregistering the event.</returns>
|
||||
public static MiraEventHandle RegisterEventHandler(Type type, MethodInfo methodInfo, int priority = 0)
|
||||
{
|
||||
if (!type.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Type must be a subclass of MiraEvent: {nameof(type)}");
|
||||
return;
|
||||
throw new InvalidOperationException($"Type must be a subclass of MiraEvent: {type.FullName}");
|
||||
}
|
||||
|
||||
if (!EventWrappers.TryGetValue(type, out var handlers))
|
||||
{
|
||||
handlers = [];
|
||||
EventWrappers.Add(type, handlers);
|
||||
}
|
||||
EventWrappers.TryAdd(type, []);
|
||||
var handlers = EventWrappers[type];
|
||||
|
||||
var @delegate = Delegate.CreateDelegate(typeof(Action<>).MakeGenericType(type), methodInfo);
|
||||
handlers.Add(new MiraEventWrapper(@delegate, priority));
|
||||
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);
|
||||
return new MiraEventHandle(type, 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
|
||||
/// <returns>An event handle to use when unregistering the event.</returns>
|
||||
public static MiraEventHandle 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);
|
||||
|
||||
var index = handlers.BinarySearch(eventWrapper, Comparer<MiraEventWrapper>.Create((a, b) => a.Priority.CompareTo(b.Priority)));
|
||||
|
||||
if (index < 0)
|
||||
{
|
||||
index = ~index;
|
||||
}
|
||||
|
||||
handlers.Insert(index, eventWrapper);
|
||||
return new MiraEventHandle(typeof(T), eventWrapper);
|
||||
}
|
||||
|
||||
internal static void SortAllHandlers()
|
||||
/// <summary>
|
||||
/// Unregister an event using the event handle.
|
||||
/// </summary>
|
||||
/// <param name="eventHandle">A handle to the event.</param>
|
||||
/// <returns>True if the event was unregistered successfully, false otherwise.</returns>
|
||||
public static bool UnregisterEventHandler(MiraEventHandle eventHandle)
|
||||
{
|
||||
foreach (var handlers in EventWrappers.Values)
|
||||
if (!EventWrappers.TryGetValue(eventHandle.EventType, out var handlers))
|
||||
{
|
||||
handlers.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!handlers.Remove(eventHandle.EventWrapper))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (handlers.Count == 0)
|
||||
{
|
||||
EventWrappers.Remove(eventHandle.EventType);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -9,14 +9,14 @@ namespace MiraAPI.Events;
|
||||
public class RegisterEventAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the priority of the event.
|
||||
/// 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.</param>
|
||||
/// <param name="priority">The priority of the event. Lower values are called first.</param>
|
||||
public RegisterEventAttribute(int priority = 0)
|
||||
{
|
||||
Priority = priority;
|
||||
|
||||
@@ -15,14 +15,21 @@ public class AfterMurderEvent : MiraEvent
|
||||
/// </summary>
|
||||
public PlayerControl Target { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player's body, if it exists.
|
||||
/// </summary>
|
||||
public DeadBody? DeadBody { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AfterMurderEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="source">The killer.</param>
|
||||
/// <param name="target">The killed player.</param>
|
||||
public AfterMurderEvent(PlayerControl source, PlayerControl target)
|
||||
/// <param name="deadBody">The player's vody, if it exists.</param>
|
||||
public AfterMurderEvent(PlayerControl source, PlayerControl target, DeadBody? deadBody)
|
||||
{
|
||||
Source = source;
|
||||
Target = target;
|
||||
DeadBody = deadBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before RpcEndGame is called, allowing cancellation of the game end entirely.
|
||||
/// </summary>
|
||||
public class BeforeGameEndEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the reason for the game end.
|
||||
/// </summary>
|
||||
public GameOverReason Reason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeGameEndEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason for the game end.</param>
|
||||
public BeforeGameEndEvent(GameOverReason reason)
|
||||
{
|
||||
Reason = reason;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked on IntroCutscene.OnDestroy and ExileController.WrapUp to determine if it should run the round event or not.
|
||||
/// </summary>
|
||||
public class BeforeRoundStartEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event was triggered by the IntroCutscene or ExileController.
|
||||
/// </summary>
|
||||
public bool TriggeredByIntro { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeRoundStartEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
|
||||
public BeforeRoundStartEvent(bool triggeredByIntro)
|
||||
{
|
||||
TriggeredByIntro = triggeredByIntro;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,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>
|
||||
/// The event that is invoked when the player's role is shown on the intro cutscene. Non cancelable.
|
||||
/// </summary>
|
||||
public class IntroRoleRevealEvent : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the IntroCutscene.
|
||||
/// </summary>
|
||||
public IntroCutscene IntroCutscene { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="IntroRoleRevealEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="introCutscene">The intro cutscene.</param>
|
||||
public IntroRoleRevealEvent(IntroCutscene introCutscene)
|
||||
{
|
||||
IntroCutscene = introCutscene;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp if the event isn't cancelled.
|
||||
/// </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;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ public class UpdateSystemEvent : MiraCancelableEvent
|
||||
/// Initializes a new instance of the <see cref="UpdateSystemEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="systemType">The SystemType being updated.</param>
|
||||
/// <param name="player">The player that is updating the system.</param>
|
||||
/// <param name="amount">Amount to update System to.</param>
|
||||
public UpdateSystemEvent(SystemTypes systemType, PlayerControl player, byte amount)
|
||||
{
|
||||
|
||||
@@ -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,30 @@
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Event that is invoked after the local player successfully votes a player or skips. This event is not cancelable.
|
||||
/// </summary>
|
||||
public class AfterVoteEvent : MiraEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the voter's vote data.
|
||||
/// </summary>
|
||||
public PlayerVoteArea VoteArea { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player who voted.
|
||||
/// </summary>
|
||||
public PlayerControl Player { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AfterVoteEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The player vote area that was voted on.</param>
|
||||
/// <param name="voter">The player who voted.</param>
|
||||
public AfterVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter)
|
||||
{
|
||||
VoteArea = playerVoteArea;
|
||||
Player = voter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Event that is invoked before the local player confirms a vote on a player or when skipping. This event is cancelable.
|
||||
/// </summary>
|
||||
public class BeforeVoteEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the voter's vote data.
|
||||
/// </summary>
|
||||
public PlayerVoteArea VoteArea { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player who voted.
|
||||
/// </summary>
|
||||
public PlayerControl Player { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeVoteEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="playerVoteArea">The player vote area that was voted on.</param>
|
||||
/// <param name="voter">The player who voted.</param>
|
||||
public BeforeVoteEvent(PlayerVoteArea playerVoteArea, PlayerControl voter)
|
||||
{
|
||||
VoteArea = playerVoteArea;
|
||||
Player = voter;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before the dummy selects their vote. If canceled, the dummy won't vote.
|
||||
/// </summary>
|
||||
public class DummyVoteEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dummy that needs to vote.
|
||||
/// </summary>
|
||||
public DummyBehaviour Dummy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the dummy's vote data.
|
||||
/// </summary>
|
||||
public PlayerVoteData VoteData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dummy can vote to skip the meeting.
|
||||
/// </summary>
|
||||
public bool CanSkip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a predicate for validating whether the dummy can vote on a player.
|
||||
/// By default, excludes dead and disconnected players.
|
||||
/// </summary>
|
||||
public Predicate<PlayerControl> PlayerIsValid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DummyVoteEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dummy">The voting dummy.</param>
|
||||
public DummyVoteEvent(DummyBehaviour dummy)
|
||||
{
|
||||
Dummy = dummy;
|
||||
VoteData = dummy.myPlayer.GetVoteData();
|
||||
CanSkip = true;
|
||||
PlayerIsValid = x => !x.Data.IsDead && !x.Data.Disconnected;
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,11 @@ public class HandleVoteEvent : MiraCancelableEvent
|
||||
/// </summary>
|
||||
public byte TargetId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether to prevent the vote from commencing.
|
||||
/// </summary>
|
||||
public bool PreventVote { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the player info of the target.
|
||||
/// </summary>
|
||||
@@ -39,6 +44,7 @@ public class HandleVoteEvent : MiraCancelableEvent
|
||||
VoteData = playerVoteData;
|
||||
Player = playerVoteData.Owner;
|
||||
TargetId = targetId;
|
||||
PreventVote = false;
|
||||
TargetPlayerInfo = GameData.Instance.GetPlayerById(targetId);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -15,14 +15,21 @@ public class PlayerDeathEvent : MiraEvent
|
||||
/// </summary>
|
||||
public DeathReason DeathReason { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the dead body associated with the player, if any.
|
||||
/// </summary>
|
||||
public DeadBody? DeadBody { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PlayerDeathEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="player">The player who died.</param>
|
||||
/// <param name="reason">The reason the player died.</param>
|
||||
public PlayerDeathEvent(PlayerControl player, DeathReason reason)
|
||||
/// <param name="deadBody">The player's body, if it exists.</param>
|
||||
public PlayerDeathEvent(PlayerControl player, DeathReason reason, DeadBody? deadBody)
|
||||
{
|
||||
Player = player;
|
||||
DeathReason = reason;
|
||||
DeadBody = deadBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
[*.cs]
|
||||
dotnet_diagnostic.CS1591.severity = none
|
||||
@@ -21,7 +21,7 @@ public static class CustomGameModeManager
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current gamemode.
|
||||
/// Gets the current gamemode.
|
||||
/// </summary>
|
||||
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
@@ -11,6 +12,11 @@ public abstract class AbstractOptionGroup
|
||||
{
|
||||
internal List<IModdedOption> Options { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of the options which are a part of this group.
|
||||
/// </summary>
|
||||
public ReadOnlyCollection<IModdedOption> Children => new(Options);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the group. Visible in options menu.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,18 +6,101 @@ using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for modded options.
|
||||
/// </summary>
|
||||
public interface IModdedOption
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for the option.
|
||||
/// </summary>
|
||||
uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the option.
|
||||
/// </summary>
|
||||
string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StringName for the option, used for localization.
|
||||
/// </summary>
|
||||
StringNames StringName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the MiraPlugin that created this option.
|
||||
/// </summary>
|
||||
IMiraPlugin? ParentMod { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the game setting data for the option.
|
||||
/// </summary>
|
||||
BaseGameSetting Data { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the OptionBehaviour object of the option.
|
||||
/// </summary>
|
||||
OptionBehaviour? OptionBehaviour { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility function for the option.
|
||||
/// </summary>
|
||||
Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the option should be included with presets.
|
||||
/// </summary>
|
||||
bool IncludeInPreset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the ConfigDefinition for the option, used for BepInEx configuration.
|
||||
/// </summary>
|
||||
ConfigDefinition? ConfigDefinition { get; set; }
|
||||
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the option behaviour for the modded option.
|
||||
/// </summary>
|
||||
/// <param name="toggleOpt">The ToggleOption template.</param>
|
||||
/// <param name="numberOpt">The NumberOption template.</param>
|
||||
/// <param name="stringOpt">The StringOption template.</param>
|
||||
/// <param name="playerOpt">The PlayerOption template.</param>
|
||||
/// <param name="container">>The Transform container for the option.</param>
|
||||
/// <returns>>The created OptionBehaviour object.</returns>
|
||||
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value as a float.
|
||||
/// </summary>
|
||||
/// <returns>>The value of the option as a float.</returns>
|
||||
float GetFloatData();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the NetData for the option, used for network synchronization.
|
||||
/// </summary>
|
||||
/// <returns>>Returns the NetData object for the option.</returns>
|
||||
NetData GetNetData();
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming network data for the option.
|
||||
/// </summary>
|
||||
/// <param name="data">>The byte array representing the network data.</param>
|
||||
void HandleNetData(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// Saves the option to a preset configuration file.
|
||||
/// </summary>
|
||||
/// <param name="presetConfig">The ConfigFile representing the preset configuration.</param>
|
||||
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
||||
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
||||
|
||||
/// <summary>
|
||||
/// Binds the option to a configuration file.
|
||||
/// </summary>
|
||||
/// <param name="config">The ConfigFile to bind the option to.</param>
|
||||
void Bind(ConfigFile config);
|
||||
|
||||
/// <summary>
|
||||
/// Loads the option from a preset configuration file, applying the values to the option's configuration.
|
||||
/// </summary>
|
||||
/// <param name="presetConfig">The ConfigFile representing the preset configuration.</param>
|
||||
void LoadFromPreset(ConfigFile presetConfig);
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx.Configuration;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -21,6 +23,7 @@ public static class ModdedOptionsManager
|
||||
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = [];
|
||||
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
||||
|
||||
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
||||
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
|
||||
internal static readonly List<AbstractOptionGroup> Groups = [];
|
||||
|
||||
@@ -42,10 +45,12 @@ public static class ModdedOptionsManager
|
||||
|
||||
Groups.Add(group);
|
||||
TypeToGroup.Add(type, group);
|
||||
pluginInfo.OptionGroups.Add(group);
|
||||
pluginInfo.InternalOptionGroups.Add(group);
|
||||
|
||||
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||
#pragma warning disable S3011
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
#pragma warning restore S3011
|
||||
.SetValue(null, group);
|
||||
|
||||
return true;
|
||||
@@ -119,7 +124,7 @@ public static class ModdedOptionsManager
|
||||
option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
|
||||
|
||||
option.ParentMod = pluginInfo.MiraPlugin;
|
||||
pluginInfo.Options.Add(option);
|
||||
pluginInfo.InternalOptions.Add(option);
|
||||
ModdedOptions.Add(option.Id, option);
|
||||
group.Options.Add(option);
|
||||
}
|
||||
@@ -140,7 +145,7 @@ public static class ModdedOptionsManager
|
||||
// we dont know how other plugins handle their configs
|
||||
// this way, all the options are saved at once, instead of one by one
|
||||
var oldConfigSetting = new Dictionary<MiraPluginInfo, bool>();
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
|
||||
plugin.PluginConfig.SaveOnConfigSet = false;
|
||||
@@ -156,7 +161,7 @@ public static class ModdedOptionsManager
|
||||
option.HandleNetData(netData.Data);
|
||||
}
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
plugin.PluginConfig.Save();
|
||||
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
|
||||
@@ -173,7 +178,10 @@ public static class ModdedOptionsManager
|
||||
/// </summary>
|
||||
/// <param name="__originalMethod">The original setter method.</param>
|
||||
/// <param name="value">The new object value.</param>
|
||||
#pragma warning disable CA1707
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
||||
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
|
||||
#pragma warning restore CA1707
|
||||
{
|
||||
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
|
||||
attribute.SetValue(value);
|
||||
@@ -185,7 +193,10 @@ public static class ModdedOptionsManager
|
||||
/// <param name="__originalMethod">The original getter method.</param>
|
||||
/// <param name="__result">The result of the property getter.</param>
|
||||
/// <returns>False so the original getter gets skipped.</returns>
|
||||
#pragma warning disable CA1707
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming", Justification = "Harmony naming convention")]
|
||||
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
|
||||
#pragma warning restore CA1707
|
||||
{
|
||||
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
|
||||
__result = attribute.GetValue();
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
#pragma warning disable CA1852
|
||||
internal class ModifierOptionGroup : AbstractOptionGroup
|
||||
#pragma warning restore CA1852
|
||||
{
|
||||
public override string GroupName { get; }
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
public string[] Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedEnumOption"/> class.
|
||||
@@ -27,7 +27,8 @@ 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>
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null) : base(title, defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(enumType);
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
@@ -37,13 +38,13 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
data.Type = global::OptionTypes.String;
|
||||
data.Values = values is null ?
|
||||
Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray()
|
||||
: values.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
: [.. values.Select(CustomStringName.CreateAndRegister)];
|
||||
|
||||
data.Index = Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
|
||||
@@ -87,7 +88,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
@@ -109,7 +110,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
public string[] Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
||||
@@ -117,7 +118,8 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null) : base(title, defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(typeof(T));
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
@@ -127,13 +129,13 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
data.Type = global::OptionTypes.String;
|
||||
data.Values = values is null ?
|
||||
Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray()
|
||||
: values.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
: [.. values.Select(CustomStringName.CreateAndRegister)];
|
||||
|
||||
data.Index = Convert.ToInt32(Value);
|
||||
data.Index = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// <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="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedNumberOption(
|
||||
string title,
|
||||
float defaultValue,
|
||||
@@ -56,7 +57,8 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
float increment,
|
||||
MiraNumberSuffixes suffixType,
|
||||
string? formatString = null,
|
||||
bool zeroInfinity = false) : base(title, defaultValue)
|
||||
bool zeroInfinity = false,
|
||||
bool includeInPreset = true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
@@ -91,6 +93,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container)
|
||||
{
|
||||
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
|
||||
|
||||
@@ -4,6 +4,7 @@ using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
@@ -16,29 +17,19 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
{
|
||||
private IMiraPlugin? _parentMod;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public uint Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public string Title { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the StringName object of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public StringNames StringName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BaseGameSetting data of the option.
|
||||
/// </summary>
|
||||
public BaseGameSetting Data { get; protected init; }
|
||||
/// <inheritdoc />
|
||||
public BaseGameSetting Data { get; protected set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the parent mod of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public IMiraPlugin? ParentMod
|
||||
{
|
||||
get => _parentMod;
|
||||
@@ -46,6 +37,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
{
|
||||
if (_parentMod != null || value == null) return;
|
||||
_parentMod = value;
|
||||
|
||||
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
|
||||
Value = entry.Value;
|
||||
}
|
||||
@@ -66,19 +58,16 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// </summary>
|
||||
public Action<T>? ChangedEvent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the visibility of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option behaviour of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public bool IncludeInPreset { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public OptionBehaviour? OptionBehaviour { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the config definition of the option.
|
||||
/// </summary>
|
||||
/// <inheritdoc />
|
||||
public ConfigDefinition? ConfigDefinition { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -86,7 +75,8 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
protected ModdedOption(string title, T defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include the option in the preset.</param>
|
||||
protected ModdedOption(string title, T defaultValue, bool includeInPreset = true)
|
||||
{
|
||||
Id = ModdedOptionsManager.NextId;
|
||||
Title = title;
|
||||
@@ -94,6 +84,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
Value = defaultValue;
|
||||
StringName = CustomStringName.CreateAndRegister(Title);
|
||||
Visible = () => true;
|
||||
IncludeInPreset = includeInPreset;
|
||||
}
|
||||
|
||||
internal void ValueChanged(OptionBehaviour optionBehaviour)
|
||||
@@ -105,7 +96,8 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// Sets the value of the option.
|
||||
/// </summary>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
public void SetValue(T newValue)
|
||||
/// <param name="sendRpc">Whether to send the value to other players.</param>
|
||||
public void SetValue(T newValue, bool sendRpc = true)
|
||||
{
|
||||
var oldVal = Value;
|
||||
Value = newValue;
|
||||
@@ -115,7 +107,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
ChangedEvent?.Invoke(Value);
|
||||
}
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
if (sendRpc && AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
|
||||
{
|
||||
@@ -128,22 +120,44 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
OnValueChanged(newValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the float data of the option.
|
||||
/// </summary>
|
||||
/// <returns>A float object representing the option's value.</returns>
|
||||
/// <inheritdoc />
|
||||
public void SaveToPreset(ConfigFile presetConfig, bool saveDefault=false)
|
||||
{
|
||||
if (ConfigDefinition is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
|
||||
return;
|
||||
}
|
||||
Bind(presetConfig);
|
||||
presetConfig[ConfigDefinition].BoxedValue = saveDefault ? DefaultValue : Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Bind(ConfigFile config)
|
||||
{
|
||||
config.Bind(ConfigDefinition, DefaultValue);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void LoadFromPreset(ConfigFile presetConfig)
|
||||
{
|
||||
if (presetConfig.TryGetEntry(ConfigDefinition, out ConfigEntry<T> entry))
|
||||
{
|
||||
SetValue(entry.Value, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract float GetFloatData();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the net data of the option.
|
||||
/// </summary>
|
||||
/// <returns>A NetData object representing this option's data.</returns>
|
||||
/// <inheritdoc />
|
||||
public abstract NetData GetNetData();
|
||||
|
||||
/// <summary>
|
||||
/// Handles incoming net data.
|
||||
/// </summary>
|
||||
/// <param name="data">The NetData's byte array.</param>
|
||||
/// <inheritdoc />
|
||||
public abstract void HandleNetData(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
@@ -159,18 +173,12 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
/// <returns>The value.</returns>
|
||||
public abstract T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour);
|
||||
|
||||
/// <summary>
|
||||
/// Creates the option behaviour.
|
||||
/// </summary>
|
||||
/// <param name="toggleOpt">The ToggleOption prefab.</param>
|
||||
/// <param name="numberOpt">The NumberOption prefab.</param>
|
||||
/// <param name="stringOpt">The StringOption prefab.</param>
|
||||
/// <param name="container">The options container.</param>
|
||||
/// <returns>A new OptionBehaviour for this modded option.</returns>
|
||||
/// <inheritdoc />
|
||||
public abstract OptionBehaviour CreateOption(
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// An option for selecting an ingame player. It returns an index of the values list, NOT A PLAYER ID. To get a player out of this, index the Values list.
|
||||
/// </summary>
|
||||
public class ModdedPlayerOption : ModdedOption<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the included players.
|
||||
/// </summary>
|
||||
public List<NetworkedPlayerInfo> Values { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a filter for the players included in the option.
|
||||
/// </summary>
|
||||
public Func<NetworkedPlayerInfo, bool>? PlayerFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the option includes a "None" option. If None is selected, value will return -1.
|
||||
/// </summary>
|
||||
public bool AllowNone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="allowNone">Whether the option includes a none option. If None is selected, value will return -1.</param>
|
||||
public ModdedPlayerOption(string title, bool allowNone = true) : base(title, allowNone ? -1 : 0, false)
|
||||
{
|
||||
Data = ScriptableObject.CreateInstance<PlayerSelectionGameSetting>();
|
||||
var data = (PlayerSelectionGameSetting)Data;
|
||||
|
||||
data.Title = StringName;
|
||||
data.Type = global::OptionTypes.Player;
|
||||
|
||||
Values = new List<NetworkedPlayerInfo>();
|
||||
AllowNone = allowNone;
|
||||
Value = DefaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container)
|
||||
{
|
||||
var playerOption = Object.Instantiate(playerOpt, container);
|
||||
|
||||
var filteredList = GetFilteredPlayers();
|
||||
Values = filteredList.ToIl2CppList();
|
||||
|
||||
playerOption.SetUpFromData(Data, 20);
|
||||
playerOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
playerOption.Title = StringName;
|
||||
playerOption.TitleText.text = TranslationController.Instance.GetString(StringName);
|
||||
playerOption.Values = Values;
|
||||
playerOption.Value = Value;
|
||||
playerOption.playerIndex = filteredList.FindIndex(p => p.PlayerId == Value);
|
||||
|
||||
OptionBehaviour = playerOption;
|
||||
ModdedOptionsManager.CreatedPlayerOptions.TryAdd(playerOption, this);
|
||||
|
||||
playerOption.SetValueText();
|
||||
playerOption.AdjustButtonsActiveState();
|
||||
|
||||
return playerOption;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a player ran through the filter.
|
||||
/// </summary>
|
||||
/// <returns>A list of filtered players.</returns>
|
||||
public System.Collections.Generic.List<NetworkedPlayerInfo> GetFilteredPlayers()
|
||||
{
|
||||
return GameData.Instance.AllPlayers.ToArray()
|
||||
.Where(x => PlayerFilter?.Invoke(x) ?? true)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a player from the value. If AllowNone is enabled and none is enabled this will return null.
|
||||
/// </summary>
|
||||
/// <returns>A player or null if chosen none.</returns>
|
||||
public NetworkedPlayerInfo? GetPlayerValue()
|
||||
{
|
||||
return Value < 0 ? null : Values[Value];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override NetData GetNetData()
|
||||
{
|
||||
return new NetData(Id, BitConverter.GetBytes(Value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleNetData(byte[] data)
|
||||
{
|
||||
SetValue(BitConverter.ToInt32(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return optionBehaviour.GetInt();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is PlayerOption opt)
|
||||
{
|
||||
opt.Value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
/// </summary>
|
||||
/// <param name="title">The option title.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
public ModdedToggleOption(string title, bool defaultValue) : base(title, defaultValue)
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedToggleOption(string title, bool defaultValue, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
|
||||
|
||||
@@ -25,7 +26,7 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// The uses mode for custom buttons.
|
||||
/// </summary>
|
||||
public enum ButtonUsesMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Uses per game.
|
||||
/// </summary>
|
||||
PerGame,
|
||||
|
||||
/// <summary>
|
||||
/// Uses per round.
|
||||
/// </summary>
|
||||
PerRound,
|
||||
}
|
||||
@@ -2,8 +2,13 @@
|
||||
using System.Globalization;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.Patches;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
@@ -31,13 +36,29 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public abstract float Cooldown { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the timer should stop decreasing when the player is venting.
|
||||
/// Note that both cooldown decrease and effect timer decrease is disabled.
|
||||
/// </summary>
|
||||
public virtual bool PauseTimerInVent => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite of the button. Use <see cref="LoadableResourceAsset"/> to load a sprite from a resource path. Use <see cref="LoadableBundleAsset{T}"/> to load a sprite from an asset bundle.
|
||||
/// </summary>
|
||||
public abstract LoadableAsset<Sprite> Sprite { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's effect duration in seconds. If the button has no effect, set to 0.
|
||||
/// Gets the format string for the cooldown timer.
|
||||
/// </summary>
|
||||
public virtual string CooldownTimerFormatString => "0";
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the button has an effect ability. By default true if <see cref="EffectDuration"/> is greater than 0.
|
||||
/// </summary>
|
||||
public virtual bool HasEffect => EffectDuration > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's effect duration in seconds. If 0, the effect needs to be ended manually.
|
||||
/// </summary>
|
||||
public virtual float EffectDuration => 0;
|
||||
|
||||
@@ -46,6 +67,16 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public virtual int MaxUses => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value indicating uses mode.
|
||||
/// </summary>
|
||||
public virtual ButtonUsesMode UsesMode => ButtonUsesMode.PerGame;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keybind for this button. If null, no keybind will be added.
|
||||
/// </summary>
|
||||
public virtual MiraKeybind? Keybind => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's text outline color.
|
||||
/// </summary>
|
||||
@@ -56,11 +87,6 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public virtual ButtonLocation Location { get; set; } = ButtonLocation.BottomLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the button has an effect ability.
|
||||
/// </summary>
|
||||
public bool HasEffect => EffectDuration > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the button has limited uses.
|
||||
/// </summary>
|
||||
@@ -91,6 +117,16 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public ActionButton? Button { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the gameObject used for the keybind icon.
|
||||
/// </summary>
|
||||
public GameObject? KeybindIcon { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the keybind icon text.
|
||||
/// </summary>
|
||||
private TextMeshPro? KeybindText { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The method used to create the button.
|
||||
/// </summary>
|
||||
@@ -162,6 +198,26 @@ public abstract class CustomActionButton
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
if (Keybind != null)
|
||||
{
|
||||
Keybind.OnActivate(() =>
|
||||
{
|
||||
if (Enabled(PlayerControl.LocalPlayer.Data.Role))
|
||||
{
|
||||
ClickHandler();
|
||||
}
|
||||
});
|
||||
|
||||
KeybindIcon =
|
||||
Helpers.CreateKeybindIcon(
|
||||
Button.gameObject,
|
||||
Keybind.CurrentKey,
|
||||
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
|
||||
);
|
||||
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -178,9 +234,12 @@ public abstract class CustomActionButton
|
||||
|
||||
this.Location = location;
|
||||
|
||||
if (!moveButton) return;
|
||||
if (!moveButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HudManagerPatches._bottomLeft == null || HudManagerPatches._bottomRight == null)
|
||||
if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -188,17 +247,17 @@ public abstract class CustomActionButton
|
||||
switch (location)
|
||||
{
|
||||
case ButtonLocation.BottomLeft:
|
||||
var gridArrange = HudManagerPatches._bottomLeft.GetComponent<GridArrange>();
|
||||
var aspectPosition = HudManagerPatches._bottomLeft.GetComponent<AspectPosition>();
|
||||
var gridArrange = HudManagerPatches.BottomLeft.GetComponent<GridArrange>();
|
||||
var aspectPosition = HudManagerPatches.BottomLeft.GetComponent<AspectPosition>();
|
||||
|
||||
Button.transform.SetParent(HudManagerPatches._bottomLeft.transform);
|
||||
Button.transform.SetParent(HudManagerPatches.BottomLeft.transform);
|
||||
|
||||
gridArrange.Start();
|
||||
gridArrange.ArrangeChilds();
|
||||
aspectPosition.AdjustPosition();
|
||||
break;
|
||||
case ButtonLocation.BottomRight:
|
||||
Button.transform.SetParent(HudManagerPatches._bottomRight);
|
||||
Button.transform.SetParent(HudManagerPatches.BottomRight);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -345,6 +404,13 @@ public abstract class CustomActionButton
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a value indicating whether the effect can be ended early by the player.
|
||||
/// Always false by default.
|
||||
/// </summary>
|
||||
/// <returns>Can the effect be canceled.</returns>
|
||||
public virtual bool IsEffectCancellable() => false;
|
||||
|
||||
/// <summary>
|
||||
/// When the button is usable, this method is called to determine if the button can be clicked.
|
||||
/// By default, it takes into account the timer, effect, and uses.
|
||||
@@ -353,7 +419,7 @@ public abstract class CustomActionButton
|
||||
/// <returns>A value that represents whether the button can be clicked or not.</returns>
|
||||
public virtual bool CanClick()
|
||||
{
|
||||
return Timer <= 0 && !EffectActive && CanUse();
|
||||
return (EffectActive ? IsEffectCancellable() : Timer <= 0) && CanUse();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -363,7 +429,8 @@ public abstract class CustomActionButton
|
||||
/// <returns>A value that represents whether the button should light up or not.</returns>
|
||||
public virtual bool CanUse()
|
||||
{
|
||||
return PlayerControl.LocalPlayer.moveable && (!LimitedUses || UsesLeft > 0);
|
||||
return PlayerControl.LocalPlayer.moveable &&
|
||||
((EffectActive && IsEffectCancellable()) || (!EffectActive && (!LimitedUses || UsesLeft > 0)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -385,6 +452,12 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public virtual void ClickHandler()
|
||||
{
|
||||
if (EffectActive && IsEffectCancellable())
|
||||
{
|
||||
ResetCooldownAndOrEffect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CanClick())
|
||||
{
|
||||
return;
|
||||
@@ -417,36 +490,49 @@ public abstract class CustomActionButton
|
||||
/// <param name="playerControl">The local PlayerControl.</param>
|
||||
public virtual void FixedUpdateHandler(PlayerControl playerControl)
|
||||
{
|
||||
if (Timer >= 0 && !TimerPaused)
|
||||
if (Keybind != null && KeybindText != null)
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
KeybindText.text = Keybind.CurrentKey.ToString();
|
||||
KeybindIcon?.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value);
|
||||
}
|
||||
else if (HasEffect && EffectActive)
|
||||
|
||||
if (Timer >= 0)
|
||||
{
|
||||
if (!TimerPaused && (!PauseTimerInVent || !playerControl.inVent))
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
}
|
||||
}
|
||||
else if (HasEffect && EffectActive && EffectDuration > 0)
|
||||
{
|
||||
EffectActive = false;
|
||||
Timer = Cooldown;
|
||||
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 && EffectDuration > 0)
|
||||
{
|
||||
Button.SetFillUp(Timer, EffectDuration);
|
||||
|
||||
Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString(NumberFormatInfo.InvariantInfo);
|
||||
Button!.cooldownTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Button?.SetCoolDown(Timer, Cooldown);
|
||||
Button.cooldownTimerText.text = Timer.ToString(CooldownTimerFormatString, NumberFormatInfo.InvariantInfo);
|
||||
Button.cooldownTimerText.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetCooldownFormat(Timer, Cooldown, CooldownTimerFormatString);
|
||||
}
|
||||
}
|
||||
|
||||
FixedUpdate(playerControl);
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
|
||||
using System.Reflection;
|
||||
using MiraAPI.Events.Mira;
|
||||
using MiraAPI.PluginLoading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
@@ -29,9 +30,11 @@ public static class CustomButtonManager
|
||||
}
|
||||
|
||||
CustomButtons.Add(button);
|
||||
pluginInfo.Buttons.Add(button);
|
||||
pluginInfo.InternalButtons.Add(button);
|
||||
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
|
||||
#pragma warning disable S3011
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
#pragma warning restore S3011
|
||||
.SetValue(null, button);
|
||||
|
||||
ButtonEventTypes.Add(buttonType, typeof(MiraButtonClickEvent<>).MakeGenericType(buttonType));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
@@ -13,7 +14,6 @@ public static class CustomButtonSingleton<T> where T : CustomActionButton
|
||||
/// <summary>
|
||||
/// Gets the instance of the button.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1000
|
||||
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a utility class to get the instance of a custom action button.")]
|
||||
public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
|
||||
#pragma warning restore CA1000
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Custom action button that has multiple target objects.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the target objects.</typeparam>
|
||||
public abstract class MultiTargetButton<T> : CustomActionButton where T : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the list of targets of the button.
|
||||
/// </summary>
|
||||
public T[] Targets { get; protected set; } = [];
|
||||
|
||||
/// <inheritdoc cref="CustomActionButton{T}.Distance"/>
|
||||
public virtual float Distance => PlayerControl.LocalPlayer.Data.Role.GetAbilityDistance();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum amount of targets the button can have.
|
||||
/// </summary>
|
||||
public virtual int MaxTargets { get; } = int.MaxValue;
|
||||
|
||||
/// <inheritdoc cref="CustomActionButton{T}.IsTargetValid"/>
|
||||
public virtual bool IsTargetValid(T target)
|
||||
{
|
||||
return target != null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The method used to get the target objects.
|
||||
/// </summary>
|
||||
/// <returns>The list of target objects.</returns>
|
||||
public abstract T[] GetTargets();
|
||||
|
||||
/// <inheritdoc cref="CustomActionButton{T}.SetOutline"/>
|
||||
/// <param name="target">The target object to set the oultine.</param>
|
||||
public abstract void SetOutline(T target, bool active);
|
||||
|
||||
/// <inheritdoc cref="CustomActionButton{T}.CanUse"/>
|
||||
public override bool CanUse()
|
||||
{
|
||||
var newTargets = GetTargets();
|
||||
foreach (var target in Targets)
|
||||
{
|
||||
if (!newTargets.Contains(target))
|
||||
{
|
||||
SetOutline(target, false);
|
||||
}
|
||||
}
|
||||
|
||||
Targets = newTargets
|
||||
.Where(IsTargetValid)
|
||||
.Take(MaxTargets)
|
||||
.ToArray();
|
||||
Targets.Do(t => SetOutline(t, true));
|
||||
|
||||
return base.CanUse() && Targets.Length > 0;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="CustomActionButton{T}.CanClick"/>
|
||||
public override bool CanClick()
|
||||
{
|
||||
return base.CanClick() && Targets.Length > 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to reset the button's targets after used.
|
||||
/// </summary>
|
||||
public virtual void ResetTarget()
|
||||
{
|
||||
Targets.Do(t => SetOutline(t, false));
|
||||
Targets = [];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Lets you register and manage mod keybinds with conflict checks and rebinding support.
|
||||
/// </summary>
|
||||
public static class KeybindManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a list of all registered keybinds.
|
||||
/// </summary>
|
||||
public static List<MiraKeybind> Keybinds { get; } = new();
|
||||
|
||||
internal static void RewiredInit()
|
||||
{
|
||||
try
|
||||
{
|
||||
var instance = KeybindUtils.RewiredInputManager!;
|
||||
foreach (var keybind in KeybindManager.Keybinds)
|
||||
{
|
||||
if (instance.userData.actions.ToArray().Any(x => x.name == keybind.Id))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Keybind of id {keybind.Id} already exists. Skipping it");
|
||||
continue;
|
||||
}
|
||||
|
||||
keybind.RewiredInputAction = instance.userData.RegisterModBind(
|
||||
keybind.Id,
|
||||
keybind.Name,
|
||||
keybind.SourcePluginName,
|
||||
keybind.DefaultKey,
|
||||
modifiers: keybind.ModifierKeys);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while registering keybinds in Rewired: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all conflicts where keybinds use the same key.
|
||||
/// </summary>
|
||||
/// <returns>A list of pairs of keybinds that conflict.</returns>
|
||||
public static Dictionary<KeyboardKeyCode, List<MiraKeybind>> GetConflicts()
|
||||
{
|
||||
var conflicts = new Dictionary<KeyboardKeyCode, List<MiraKeybind>>();
|
||||
foreach (var keybind in Keybinds)
|
||||
{
|
||||
var all = GetKeybindsForKey(keybind.CurrentKey, true);
|
||||
if (all.Length > 1)
|
||||
{
|
||||
if (!conflicts.TryGetValue(keybind.CurrentKey, out var group))
|
||||
{
|
||||
conflicts.Add(keybind.CurrentKey, new List<MiraKeybind>());
|
||||
group = conflicts[keybind.CurrentKey];
|
||||
}
|
||||
|
||||
if (!group.Contains(keybind))
|
||||
{
|
||||
group.Add(keybind);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all keybinds for a specified keycode.
|
||||
/// </summary>
|
||||
/// <param name="keyCode">The key to look for.</param>
|
||||
/// /// <param name="exclusiveCheck">Should only exlusive keybinds be present.</param>
|
||||
/// <returns>The list of keybinds.</returns>
|
||||
public static MiraKeybind[] GetKeybindsForKey(KeyboardKeyCode keyCode, bool exclusiveCheck = false)
|
||||
{
|
||||
var all = new List<MiraKeybind>();
|
||||
foreach (var keybind in Keybinds)
|
||||
{
|
||||
if (keybind.CurrentKey == keyCode && (keybind.Exclusive || !exclusiveCheck))
|
||||
{
|
||||
all.Add(keybind);
|
||||
}
|
||||
}
|
||||
|
||||
return all.ToArray();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Collection of keybind-related utility classes.
|
||||
/// </summary>
|
||||
public static class KeybindUtils
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Rewired <see cref="InputManager_Base"/> instance.
|
||||
/// </summary>
|
||||
public static InputManager_Base? RewiredInputManager { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the currently assigned keycode of a keybind.
|
||||
/// </summary>
|
||||
/// <param name="keybind">The keybind to get the keycode from.</param>
|
||||
/// <returns>The currently assigned keycode.</returns>
|
||||
public static KeyboardKeyCode? GetKeycodeByKeybind(MiraKeybind keybind)
|
||||
{
|
||||
if (keybind.RewiredInputAction == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return GetKeycodeByActionId(keybind.RewiredInputAction.id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keycode for an action with ReInput.
|
||||
/// </summary>
|
||||
/// <param name="actionId">The action ID.</param>
|
||||
/// <returns>The keyboard keycode.</returns>
|
||||
public static KeyboardKeyCode GetKeycodeByActionId(int actionId)
|
||||
{
|
||||
var player = ReInput.players.GetPlayer(0);
|
||||
return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false)?
|
||||
.keyboardKeyCode ?? KeyboardKeyCode.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and returns an unused KeyCode that is not equal to the excluded key.
|
||||
/// </summary>
|
||||
/// <param name="exclude">The KeyCode to skip during the search.</param>
|
||||
/// <returns>
|
||||
/// The first available KeyCode not currently used by any registered keybind,
|
||||
/// or KeyCode.None if none are available.
|
||||
/// </returns>
|
||||
public static KeyboardKeyCode FindAvailableKey(KeyboardKeyCode exclude)
|
||||
{
|
||||
foreach (KeyboardKeyCode key in Enum.GetValues<KeyboardKeyCode>())
|
||||
{
|
||||
if (key == exclude) continue;
|
||||
bool used = KeybindManager.Keybinds.Exists(e => e.DefaultKey == key);
|
||||
if (!used) return key;
|
||||
}
|
||||
return KeyboardKeyCode.None;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Class containing all default keybinds.
|
||||
/// Recommended for cross-mod compatibility.
|
||||
/// </summary>
|
||||
[RegisterCustomKeybinds]
|
||||
public static class MiraGlobalKeybinds
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the keybind for primary abilities.
|
||||
/// </summary>
|
||||
public static MiraKeybind PrimaryAbility { get; } = new("Primary Ability", KeyboardKeyCode.T);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keybind for secondary abilities.
|
||||
/// </summary>
|
||||
public static MiraKeybind SecondaryAbility { get; } = new("Secondary Ability", KeyboardKeyCode.Y);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keybind for Tetiary abilities.
|
||||
/// </summary>
|
||||
public static MiraKeybind TertiaryAbility { get; } = new("Tertiary Ability", KeyboardKeyCode.U);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keybind for primary modifier abilities.
|
||||
/// </summary>
|
||||
public static MiraKeybind ModifierPrimaryAbility { get; } = new("Modifier Primary Ability", KeyboardKeyCode.I);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keybind for secondary modifier abilities.
|
||||
/// </summary>
|
||||
public static MiraKeybind ModifierSecondaryAbility { get; } = new("Modifier Secondary Ability", KeyboardKeyCode.O);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the keybind for Tetiary modifier abilities.
|
||||
/// </summary>
|
||||
public static MiraKeybind ModifierTertiaryAbility { get; } = new("Modifier Tertiary Ability", KeyboardKeyCode.P);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a registered keybind.
|
||||
/// </summary>
|
||||
public class MiraKeybind
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets name of the keybind
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this keybind. Used in Rewired.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default keycode of this keybind.
|
||||
/// </summary>
|
||||
public KeyboardKeyCode DefaultKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this keybind should be checked for conflicts with other exclusive keybinds using the same key.
|
||||
/// </summary>
|
||||
public bool Exclusive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier keys assinged to this keybind.
|
||||
/// Due to Rewired limitations, there can only be 3 modifier keys.
|
||||
/// </summary>
|
||||
public ModifierKey[] ModifierKeys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Rewired <see cref="InputAction"/> assinged for this keybind.
|
||||
/// </summary>
|
||||
public InputAction? RewiredInputAction { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently assigned keycode.
|
||||
/// </summary>
|
||||
public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this) ?? KeyboardKeyCode.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler of the keybind. Invoked when the keybind is activated.
|
||||
/// </summary>
|
||||
private Action Handler { get; set; }
|
||||
|
||||
internal string? SourcePluginName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="MiraKeybind"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the keybind.</param>
|
||||
/// <param name="defaultKeycode">The default keycode.</param>
|
||||
/// <param name="modifierKeys">Up to 3 optional modifier keys.</param>
|
||||
/// <param name="exclusive">Is exclusive.</param>
|
||||
public MiraKeybind(
|
||||
string name,
|
||||
KeyboardKeyCode? defaultKeycode,
|
||||
ModifierKey[]? modifierKeys = null,
|
||||
bool exclusive = true)
|
||||
{
|
||||
Name = name;
|
||||
Id = name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_');
|
||||
DefaultKey = defaultKeycode ?? KeyboardKeyCode.None;
|
||||
ModifierKeys = modifierKeys ?? Array.Empty<ModifierKey>();
|
||||
Exclusive = exclusive;
|
||||
Handler = () => { };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the keybind, invoking its handler.
|
||||
/// </summary>
|
||||
public void Invoke()
|
||||
{
|
||||
Handler.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="Action"/> that will be invoked when the keybind is used.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to add.</param>
|
||||
public void OnActivate(Action action)
|
||||
{
|
||||
Handler += action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an <see cref="Action"/> that would be invoked when the keybind is used.
|
||||
/// Not recommended to use - add checks in the method added in <see cref="OnActivate"/> itself rather than removing it from the keybind.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to remove.</param>
|
||||
public void RemoveOnActivate(Action action)
|
||||
{
|
||||
Handler -= action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Used to mark a class for custom keybinds registration.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterCustomKeybindsAttribute : Attribute;
|
||||
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
namespace MiraAPI.LocalSettings.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates an enum setting for the <see cref="ConfigEntry{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="names">Optional custom enum names.</param>
|
||||
/// <inheritdoc/>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class LocalEnumSettingAttribute(
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
string[]? names = null
|
||||
) : LocalSettingAttribute(name, description)
|
||||
{
|
||||
private readonly string? _name = name;
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalEnumSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalEnumSetting(tab, configEntryBase, configEntryBase.SettingType, _name, _description, names);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings.SettingTypes;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.LocalSettings.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a number setting for the <see cref="ConfigEntry{T}"/>.
|
||||
/// </summary>
|
||||
/// <param name="min">Minimum range.</param>
|
||||
/// <param name="max">Maximum range.</param>
|
||||
/// <param name="increment">Increment per use.</param>
|
||||
/// <param name="suffixType">Suffix for the value.</param>
|
||||
/// <param name="formatString">Format string used when formating.</param>
|
||||
/// <inheritdoc/>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class LocalNumberSettingAttribute(
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
float min = 1,
|
||||
float max = 5,
|
||||
float increment = 1,
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
|
||||
string? formatString = null
|
||||
) : LocalSettingAttribute(name, description)
|
||||
{
|
||||
private readonly string? _name = name;
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalNumberSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalNumberSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), increment, suffixType, formatString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
|
||||
namespace MiraAPI.LocalSettings.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all local settings attributes.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the setting. Defaults to entry key.</param>
|
||||
/// <param name="description">The description of the setting. Defalts to entry description.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public abstract class LocalSettingAttribute(
|
||||
string? name = null,
|
||||
string? description = null
|
||||
) : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns the created setting object.
|
||||
/// </summary>
|
||||
/// <param name="tab">Gets the tab where the setting is located.</param>
|
||||
/// <param name="configEntryBase">Gets the config entry the setting is attached to.</param>
|
||||
/// <returns>The created class.</returns>
|
||||
internal abstract ILocalSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
|
||||
namespace MiraAPI.LocalSettings.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Attribute for local setting buttons.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class LocalSettingsButtonAttribute : Attribute
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings.SettingTypes;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.LocalSettings.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a slider setting for the <see cref="ConfigEntry{T}"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
/// <param name="min">Minimum range.</param>
|
||||
/// <param name="max">Maximum range.</param>
|
||||
/// <param name="roundValue">Should the value be rounded.</param>
|
||||
/// <param name="suffixType">Suffix for the value.</param>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class LocalSliderSettingAttribute(
|
||||
string? name = null,
|
||||
float min = 0,
|
||||
float max = 100,
|
||||
string? description = null,
|
||||
bool displayValue = false,
|
||||
string? formatString = null,
|
||||
bool roundValue = false,
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None
|
||||
) : LocalSettingAttribute(name, description)
|
||||
{
|
||||
private readonly string? _name = name;
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalSliderSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalSliderSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), displayValue, suffixType, formatString, roundValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
namespace MiraAPI.LocalSettings.Attributes;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a toggle setting for the <see cref="ConfigEntry{T}"/>.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class LocalToggleSettingAttribute(
|
||||
string? name = null,
|
||||
string? description = null
|
||||
) : LocalSettingAttribute(name, description)
|
||||
{
|
||||
private readonly string? _name = name;
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalToggleSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalToggleSetting(tab, configEntryBase, _name, _description);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using BepInEx.Configuration;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for all local settings.
|
||||
/// </summary>
|
||||
public interface ILocalSetting
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the preferred name of a setting.
|
||||
/// </summary>
|
||||
string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of a setting.
|
||||
/// </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the setting's gameobject.
|
||||
/// </summary>
|
||||
GameObject? Setting { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the setting's config entry.
|
||||
/// </summary>
|
||||
ConfigEntryBase ConfigEntry { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Used to create the setting.
|
||||
/// </summary>
|
||||
/// <param name="toggle">Toggle template.</param>
|
||||
/// <param name="slider">Slider template.</param>
|
||||
/// <param name="parent">The parent of the setting.</param>
|
||||
/// <param name="offset">The offset.</param>
|
||||
/// <param name="order">The order.</param>
|
||||
/// <param name="last">Whether it's the last on the row.</param>
|
||||
/// <returns>The created setting.</returns>
|
||||
GameObject? CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last);
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a local settings button.
|
||||
/// </summary>
|
||||
public class LocalSettingsButton(string text, Action onClick)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the button text.
|
||||
/// </summary>
|
||||
public string Text { get; set; } = text;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the button on-click action.
|
||||
/// </summary>
|
||||
public Action OnClick { get; set; } = onClick;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tab instance this button belongs to.
|
||||
/// </summary>
|
||||
public LocalSettingsTab? Tab { get; internal set; }
|
||||
|
||||
internal GameObject CreateButton(ToggleButtonBehaviour toggle, Transform parent, ref float offset, ref int order, bool last)
|
||||
{
|
||||
var button = Object.Instantiate(toggle, parent).GetComponent<PassiveButton>();
|
||||
var tmp = button.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
|
||||
var rollover = button.GetComponent<ButtonRolloverHandler>();
|
||||
tmp.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
button.gameObject.SetActive(true);
|
||||
|
||||
var toggleComp = button.GetComponent<ToggleButtonBehaviour>();
|
||||
var background = toggleComp.Background;
|
||||
button.transform.FindChild("ButtonHighlight")?.gameObject.DestroyImmediate();
|
||||
toggleComp.Destroy();
|
||||
|
||||
if (last && order == 1)
|
||||
{
|
||||
// Button in the middle
|
||||
button.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
|
||||
}
|
||||
|
||||
tmp.text = Text;
|
||||
button.name = Text;
|
||||
button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
|
||||
rollover.OutColor = Tab!.TabAppearance.ButtonColor;
|
||||
rollover.OverColor = Tab!.TabAppearance.ButtonHoverColor;
|
||||
rollover.Target = background;
|
||||
background.color = Tab!.TabAppearance.ButtonColor;
|
||||
button.OnClick.AddListener((UnityAction)OnClick);
|
||||
|
||||
background.transform.localScale = new Vector3(1, 1.25f, 1);
|
||||
Helpers.DivideSize(button.gameObject, 1.1f);
|
||||
|
||||
order++;
|
||||
if (order > 2 && !last)
|
||||
{
|
||||
offset += 0.5f;
|
||||
order = 1;
|
||||
}
|
||||
if (last)
|
||||
offset += 0.6f;
|
||||
|
||||
return button.gameObject;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using MiraAPI.GameOptions;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Manages local settings.
|
||||
/// </summary>
|
||||
public static class LocalSettingsManager
|
||||
{
|
||||
internal static readonly Dictionary<Type, LocalSettingsTab> TypeToTab = [];
|
||||
internal static readonly List<LocalSettingsTab> Tabs = [];
|
||||
|
||||
internal static bool RegisterTab(Type type, BasePlugin pluginInfo)
|
||||
{
|
||||
if (Activator.CreateInstance(type, pluginInfo.Config) is not LocalSettingsTab tab)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (TypeToTab.ContainsKey(type))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Local settings tab {type.Name} already exists.");
|
||||
return false;
|
||||
}
|
||||
|
||||
Tabs.Add(tab);
|
||||
TypeToTab.Add(type, tab);
|
||||
|
||||
typeof(LocalSettingsTabSingleton<>).MakeGenericType(type)
|
||||
#pragma warning disable S3011
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
#pragma warning restore S3011
|
||||
.SetValue(null, tab);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using BepInEx.Configuration;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Patches.LocalSettings;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a local settings tab. Gets a button by default.
|
||||
/// </summary>
|
||||
public abstract class LocalSettingsTab(ConfigFile config)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the tab.
|
||||
/// </summary>
|
||||
public abstract string TabName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tab appearance.
|
||||
/// </summary>
|
||||
public virtual LocalSettingTabAppearance TabAppearance { get; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether a button should be created for the tab.
|
||||
/// </summary>
|
||||
public virtual bool ShouldCreateButton => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the tab should have category labels.
|
||||
/// </summary>
|
||||
protected virtual bool ShouldCreateLabels => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the mod's config file. Automatically found in the plugin loader.
|
||||
/// </summary>
|
||||
public ConfigFile Config { get; } = config ?? throw new ArgumentNullException(nameof(config));
|
||||
|
||||
/// <summary>
|
||||
/// Gets the tab button instance.
|
||||
/// </summary>
|
||||
public TabGroup? TabButton { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of settings.
|
||||
/// </summary>
|
||||
public List<ILocalSetting> Settings { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of buttons.
|
||||
/// </summary>
|
||||
public List<LocalSettingsButton> Buttons { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the index of this tab.
|
||||
/// </summary>
|
||||
protected int TabIndex => LocalSettingsManager.Tabs.IndexOf(this) + 10;
|
||||
|
||||
private Scroller? Scroller { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when an option value is changed.
|
||||
/// </summary>
|
||||
/// <param name="configEntry">The config entry which was changed.</param>
|
||||
public virtual void OnOptionChanged(ConfigEntryBase configEntry)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to open the tab.
|
||||
/// Override for custom behavior.
|
||||
/// </summary>
|
||||
public virtual void Open()
|
||||
{
|
||||
OptionsMenuPatches.Instance?.OpenTabGroup(TabIndex);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the tab <see cref="GameObject"/> and it's content.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="OptionsMenuBehaviour"/> instace.</param>
|
||||
/// <returns>The created tab <see cref="GameObject"/>.</returns>
|
||||
public virtual GameObject CreateTab(OptionsMenuBehaviour instance)
|
||||
{
|
||||
var tab = Object.Instantiate(instance.transform.FindChild("GeneralTab").gameObject, instance.transform);
|
||||
tab.name = $"{TabName}Tab";
|
||||
tab.transform.DestroyChildren();
|
||||
tab.gameObject.SetActive(false);
|
||||
|
||||
if (Scroller == null)
|
||||
{
|
||||
Scroller = Helpers.CreateScroller(tab.transform, OptionsMenuPatches.MaskCollider);
|
||||
}
|
||||
|
||||
if (TabButton != null)
|
||||
{
|
||||
TabButton.Content = tab.gameObject;
|
||||
}
|
||||
|
||||
var generalLabel = instance.transform.FindChild("GeneralTab").FindChild("ControlGroup")
|
||||
.FindChild("ControlText_TMP").gameObject;
|
||||
var toggle = instance.transform.FindChild("GeneralTab").FindChild("ChatGroup").FindChild("CensorChatButton").GetComponent<ToggleButtonBehaviour>();
|
||||
var slider = instance.transform.FindChild("GeneralTab").FindChild("SoundGroup").FindChild("SFXSlider").GetComponent<SlideBar>();
|
||||
|
||||
Dictionary<string, List<ILocalSetting>> entriesByGroup = new();
|
||||
foreach (var entry in Settings)
|
||||
{
|
||||
var group = entry.ConfigEntry.Definition.Section;
|
||||
if (!entriesByGroup.ContainsKey(group))
|
||||
entriesByGroup.Add(group, []);
|
||||
|
||||
entriesByGroup[group].Add(entry);
|
||||
}
|
||||
|
||||
float contentOffset = 0;
|
||||
var contentOrder = 1;
|
||||
var contentIndex = 1;
|
||||
|
||||
foreach (var pair in entriesByGroup)
|
||||
{
|
||||
if (ShouldCreateLabels)
|
||||
{
|
||||
CreateLabel(generalLabel, Scroller.Inner, pair.Key, ref contentOffset);
|
||||
contentOrder = 1;
|
||||
}
|
||||
|
||||
foreach (var setting in pair.Value)
|
||||
{
|
||||
var obj = setting.CreateOption(
|
||||
toggle,
|
||||
slider,
|
||||
Scroller.Inner,
|
||||
ref contentOffset,
|
||||
ref contentOrder,
|
||||
contentIndex == pair.Value.Count);
|
||||
|
||||
obj!.GetComponentsInChildren<SpriteRenderer>(true).Do(x =>
|
||||
{
|
||||
x.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||
x.sortingOrder = 150;
|
||||
x.sortingLayerName = "Default";
|
||||
});
|
||||
|
||||
obj.GetComponentsInChildren<MeshRenderer>(true).Do(x =>
|
||||
{
|
||||
x.sortingOrder = 151;
|
||||
x.sortingLayerName = "Default";
|
||||
});
|
||||
|
||||
obj.GetComponentsInChildren<PassiveButton>().Do(x => { x.ClickMask = OptionsMenuPatches.MaskCollider; });
|
||||
|
||||
contentIndex++;
|
||||
}
|
||||
|
||||
contentOrder = 1;
|
||||
}
|
||||
|
||||
contentIndex = 1;
|
||||
contentOrder = 1;
|
||||
foreach (var button in Buttons)
|
||||
{
|
||||
var obj = button.CreateButton(
|
||||
toggle,
|
||||
Scroller.Inner,
|
||||
ref contentOffset,
|
||||
ref contentOrder,
|
||||
contentIndex == Buttons.Count);
|
||||
|
||||
obj!.GetComponentsInChildren<SpriteRenderer>(true).Do(x =>
|
||||
{
|
||||
x.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||
x.sortingOrder = 150;
|
||||
x.sortingLayerName = "Default";
|
||||
});
|
||||
|
||||
obj.GetComponentsInChildren<MeshRenderer>(true).Do(x =>
|
||||
{
|
||||
x.sortingOrder = 151;
|
||||
x.sortingLayerName = "Default";
|
||||
});
|
||||
|
||||
obj.GetComponentsInChildren<PassiveButton>().Do(x => { x.ClickMask = OptionsMenuPatches.MaskCollider; });
|
||||
|
||||
contentIndex++;
|
||||
}
|
||||
|
||||
Scroller.SetBounds(new FloatRange(0, (Settings.Count + Buttons.Count) * 0.5f - 5f), new FloatRange(0, 0));
|
||||
return tab;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates the tab button.
|
||||
/// </summary>
|
||||
/// <param name="instance">The <see cref="OptionsMenuBehaviour"/> instance.</param>
|
||||
/// <param name="tabIdx">The tab index.</param>
|
||||
/// <param name="offset">The current button offset.</param>
|
||||
/// <returns>The created button <see cref="GameObject"/>.</returns>
|
||||
public virtual GameObject CreateTabButton(OptionsMenuBehaviour instance, ref int tabIdx, ref float offset)
|
||||
{
|
||||
var tabButtonObject = Object.Instantiate(instance.Tabs[0], instance.transform);
|
||||
tabButtonObject.name = $"{TabName} Button";
|
||||
tabButtonObject.transform.localPosition = new Vector3(2.4f, 2.1f - offset, 5.5f);
|
||||
tabButtonObject.transform.localScale = new Vector3(1.25f, 1.25f, 1);
|
||||
tabButtonObject.Button.color = TabAppearance.TabButtonColor;
|
||||
TabButton = tabButtonObject;
|
||||
|
||||
var tabButtonText = tabButtonObject.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
|
||||
tabButtonText.GetComponent<TextTranslatorTMP>().Destroy(); // i hate text translators
|
||||
tabButtonText.transform.localPosition = new Vector3(0.078f, 0, 0);
|
||||
tabButtonText.transform.localScale = new Vector3(0.9f, 0.9f);
|
||||
tabButtonText.alignment = TextAlignmentOptions.Right;
|
||||
tabButtonText.text = $"<b>{GetShortName(TabName)}</b>";
|
||||
|
||||
var tabButton = tabButtonObject.GetComponent<PassiveButton>();
|
||||
var rollover = tabButtonObject.Rollover;
|
||||
rollover.OverColor = TabAppearance.TabButtonHoverColor;
|
||||
rollover.OutColor = TabAppearance.TabButtonColor;
|
||||
|
||||
SpriteRenderer? tabButtonRend = null;
|
||||
if (TabAppearance.TabIcon != null)
|
||||
{
|
||||
tabButtonRend = new GameObject("sprite").AddComponent<SpriteRenderer>();
|
||||
tabButtonRend.gameObject.layer = 5; // ui layer
|
||||
tabButtonRend.transform.SetParent(tabButtonObject.transform);
|
||||
tabButtonRend.transform.localPosition = new Vector3(0.5f, 0, -2);
|
||||
tabButtonRend.transform.localScale = new Vector3(0.2f, 0.2f, 1);
|
||||
tabButtonRend.sprite = TabAppearance.TabIcon.LoadAsset();
|
||||
tabButtonText.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
var tabIndex = tabIdx; // Local copies of the tab variables so we can use them in the lambdas
|
||||
var tabOffset = offset;
|
||||
tabButton.OnClick.AddListener((UnityAction)(() => { instance.OpenTabGroup(tabIndex + 10); }));
|
||||
tabButton.OnMouseOver.AddListener((UnityAction)(() =>
|
||||
{
|
||||
tabButton.transform.localPosition = new Vector3(3.55f, 2.1f - tabOffset, 5.5f);
|
||||
tabButtonText.gameObject.SetActive(true);
|
||||
tabButtonText.transform.localPosition = new Vector3(-0.024f, 0, 0);
|
||||
tabButtonText.maxVisibleCharacters = int.MaxValue;
|
||||
tabButtonText.text = TabName;
|
||||
tabButtonText.alignment = TextAlignmentOptions.Left;
|
||||
}));
|
||||
tabButton.OnMouseOut.AddListener((UnityAction)(() =>
|
||||
{
|
||||
if (!tabButtonObject.Content.gameObject.activeSelf)
|
||||
{
|
||||
//tabButtonObject.Button.color = TabColor;
|
||||
//tabButtonObject.Rollover.OutColor = TabColor;
|
||||
}
|
||||
|
||||
tabButton.transform.localPosition = new Vector3(2.4f, 2.1f - tabOffset, 5.5f);
|
||||
tabButtonText.alignment = TextAlignmentOptions.Right;
|
||||
if (tabButtonRend != null)
|
||||
{
|
||||
tabButtonRend.gameObject.SetActive(true);
|
||||
tabButtonText.gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
tabButtonText.transform.localPosition = new Vector3(0.1f, 0, 0);
|
||||
tabButtonText.maxVisibleCharacters = 4;
|
||||
tabButtonText.text = $"<b>{GetShortName(TabName)}</b>";
|
||||
}));
|
||||
|
||||
return tabButtonObject.gameObject;
|
||||
}
|
||||
|
||||
private static void CreateLabel(GameObject template, Transform parent, string text, ref float offset)
|
||||
{
|
||||
var label = Object.Instantiate(template, parent);
|
||||
label.transform.localPosition = new Vector3(0, 1.85f - offset);
|
||||
label.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
label.name = text;
|
||||
|
||||
var tmp = label.GetComponent<TextMeshPro>();
|
||||
tmp.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\"><b>{text}</b></font>";
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var meshRend = label.GetComponent<MeshRenderer>();
|
||||
meshRend.sortingLayerName = "Default";
|
||||
meshRend.sortingOrder = 151;
|
||||
|
||||
offset += 0.5f;
|
||||
}
|
||||
|
||||
private static string GetShortName(string name)
|
||||
{
|
||||
var shortName = string.Empty;
|
||||
name.Split(' ').Do(x => shortName += x[0]);
|
||||
return shortName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Struct for modyfing tab apparance.
|
||||
/// Defines how the settings look.
|
||||
/// </summary>
|
||||
public record struct LocalSettingTabAppearance()
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets main color of the tab.
|
||||
/// </summary>
|
||||
public Color TabColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets tab button icon. No icon by default.
|
||||
/// </summary>
|
||||
public LoadableAsset<Sprite>? TabIcon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets color of the tab button.
|
||||
/// </summary>
|
||||
public Color TabButtonColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of the tab button.
|
||||
/// </summary>
|
||||
public Color TabButtonHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets active color of the tab button.
|
||||
/// </summary>
|
||||
public Color TabButtonActiveColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of toggles.
|
||||
/// </summary>
|
||||
public Color ToggleHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets active color of toggles.
|
||||
/// </summary>
|
||||
public Color ToggleActiveColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets inactive color of toggles.
|
||||
/// </summary>
|
||||
public Color ToggleInactiveColor { get; set; } = Color.red;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets color of number buttons.
|
||||
/// </summary>
|
||||
public Color NumberColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of number buttons.
|
||||
/// </summary>
|
||||
public Color NumberHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets color of sliders.
|
||||
/// </summary>
|
||||
public Color SliderColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of sliders.
|
||||
/// </summary>
|
||||
public Color SliderHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets color of enum buttons.
|
||||
/// </summary>
|
||||
public Color EnumColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of enum buttons.
|
||||
/// </summary>
|
||||
public Color EnumHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets color of buttons.
|
||||
/// </summary>
|
||||
public Color ButtonColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of buttons.
|
||||
/// </summary>
|
||||
public Color ButtonHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton for local settings tabs.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The settings tab type.</typeparam>
|
||||
public static class LocalSettingsTabSingleton<T> where T : LocalSettingsTab
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the setting tab.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1000
|
||||
public static T Instance => _instance ??= LocalSettingsManager.Tabs.OfType<T>().Single();
|
||||
#pragma warning restore CA1000
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Local setting class for enums.
|
||||
/// </summary>
|
||||
public class LocalEnumSetting : LocalSettingBase<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the enum type of the setting.
|
||||
/// </summary>
|
||||
public Type EnumType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the enum values.
|
||||
/// </summary>
|
||||
public string[] Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalEnumSetting"/> class.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
/// <param name="enumType">The enum type.</param>
|
||||
/// <param name="values">The optional values array to replace the enum names.</param>
|
||||
public LocalEnumSetting(
|
||||
Type tab,
|
||||
ConfigEntryBase configEntry,
|
||||
Type enumType,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
string[]? values = null)
|
||||
: base(tab, configEntry, name, description)
|
||||
{
|
||||
EnumType = enumType;
|
||||
Values = values ?? Enum
|
||||
.GetValues(configEntry.SettingType)
|
||||
.Cast<Enum>()
|
||||
.Select(x => x.ToDisplayString())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
|
||||
{
|
||||
var button = Object.Instantiate(toggle, parent).GetComponent<PassiveButton>();
|
||||
var tmp = button.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
|
||||
var rollover = button.GetComponent<ButtonRolloverHandler>();
|
||||
tmp.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
button.gameObject.SetActive(true);
|
||||
|
||||
var toggleComp = button.GetComponent<ToggleButtonBehaviour>();
|
||||
var background = toggleComp.Background;
|
||||
var highlight = button.transform.FindChild("ButtonHighlight")?.GetComponent<SpriteRenderer>();
|
||||
if (highlight != null)
|
||||
{
|
||||
highlight.color = Tab!.TabAppearance.EnumHoverColor;
|
||||
highlight.gameObject.SetActive(false);
|
||||
}
|
||||
toggleComp.Destroy();
|
||||
|
||||
if (last && order == 1)
|
||||
{
|
||||
// Button in the middle
|
||||
button.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
|
||||
}
|
||||
|
||||
tmp.text = GetValueText();
|
||||
button.name = Name;
|
||||
button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
|
||||
rollover.OutColor = Tab!.TabAppearance.EnumColor;
|
||||
rollover.OverColor = Tab!.TabAppearance.EnumHoverColor;
|
||||
background.color = Tab!.TabAppearance.EnumColor;
|
||||
|
||||
button.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
int value = GetValue();
|
||||
value++;
|
||||
if (value >= Values.Length)
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
|
||||
SetValue(value);
|
||||
tmp.text = GetValueText();
|
||||
}));
|
||||
button.OnMouseOver.AddListener((UnityAction)(() =>
|
||||
{
|
||||
if (!Description.IsNullOrWhiteSpace())
|
||||
{
|
||||
tmp.text = Description;
|
||||
}
|
||||
highlight?.gameObject.SetActive(true);
|
||||
}));
|
||||
button.OnMouseOut.AddListener((UnityAction)(() =>
|
||||
{
|
||||
tmp.text = GetValueText();
|
||||
highlight?.gameObject.SetActive(false);
|
||||
}));
|
||||
|
||||
Helpers.DivideSize(button.gameObject, 1.1f);
|
||||
|
||||
order++;
|
||||
if (order > 2 && !last)
|
||||
{
|
||||
offset += 0.5f;
|
||||
order = 1;
|
||||
}
|
||||
if (last)
|
||||
offset += 0.6f;
|
||||
|
||||
return button.gameObject;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetValueText()
|
||||
{
|
||||
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}: <b>{Values[GetValue()]}</font></b>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Local setting class for numbers.
|
||||
/// </summary>
|
||||
public class LocalNumberSetting : LocalSettingBase<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the range of the button.
|
||||
/// </summary>
|
||||
public FloatRange NumberRange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the increment of the value when button is pressed.
|
||||
/// </summary>
|
||||
public float Increment { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a format for the text to use to format the number.
|
||||
/// </summary>
|
||||
public string FormatString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suffix for the number value.
|
||||
/// </summary>
|
||||
public MiraNumberSuffixes SuffixType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalNumberSetting"/> class.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
/// <param name="numberRange">The value range.</param>
|
||||
/// <param name="increment">The increment per click.</param>
|
||||
/// <param name="suffixType">The suffix used for formating.</param>
|
||||
/// <param name="formatString">The format string used for formating.</param>
|
||||
public LocalNumberSetting(
|
||||
Type tab,
|
||||
ConfigEntryBase configEntry,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
FloatRange? numberRange = null,
|
||||
float? increment = null,
|
||||
MiraNumberSuffixes? suffixType = null,
|
||||
string? formatString = null)
|
||||
: base(tab, configEntry, name, description)
|
||||
{
|
||||
SuffixType = suffixType ?? MiraNumberSuffixes.None;
|
||||
NumberRange = numberRange ?? new FloatRange(1, 5);
|
||||
Increment = increment ?? 1;
|
||||
FormatString = formatString ?? "0";
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
|
||||
{
|
||||
var button = Object.Instantiate(toggle, parent).GetComponent<PassiveButton>();
|
||||
var tmp = button.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
|
||||
var rollover = button.GetComponent<ButtonRolloverHandler>();
|
||||
tmp.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
button.gameObject.SetActive(true);
|
||||
|
||||
var toggleComp = button.GetComponent<ToggleButtonBehaviour>();
|
||||
var background = toggleComp.Background;
|
||||
var highlight = button.transform.FindChild("ButtonHighlight")?.GetComponent<SpriteRenderer>();
|
||||
if (highlight != null)
|
||||
{
|
||||
highlight.color = Tab!.TabAppearance.NumberHoverColor;
|
||||
highlight.gameObject.SetActive(false);
|
||||
}
|
||||
toggleComp.Destroy();
|
||||
|
||||
if (last && order == 1)
|
||||
{
|
||||
// Button in the middle
|
||||
button.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
|
||||
}
|
||||
else
|
||||
{
|
||||
button.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
|
||||
}
|
||||
|
||||
tmp.text = GetValueText();
|
||||
button.name = Name;
|
||||
button.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
|
||||
rollover.OutColor = Tab!.TabAppearance.NumberColor;
|
||||
rollover.OverColor = Tab!.TabAppearance.NumberHoverColor;
|
||||
background.color = Tab!.TabAppearance.NumberColor;
|
||||
|
||||
button.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
float value = GetValue();
|
||||
value += Increment;
|
||||
if (value > NumberRange.max)
|
||||
{
|
||||
value = NumberRange.min;
|
||||
}
|
||||
|
||||
SetValue(value);
|
||||
tmp.text = GetValueText();
|
||||
}));
|
||||
button.OnMouseOver.AddListener((UnityAction)(() =>
|
||||
{
|
||||
if (!Description.IsNullOrWhiteSpace())
|
||||
{
|
||||
tmp.text = Description;
|
||||
}
|
||||
highlight?.gameObject.SetActive(true);
|
||||
}));
|
||||
button.OnMouseOut.AddListener((UnityAction)(() =>
|
||||
{
|
||||
tmp.text = GetValueText();
|
||||
highlight?.gameObject.SetActive(false);
|
||||
}));
|
||||
|
||||
Helpers.DivideSize(button.gameObject, 1.1f);
|
||||
|
||||
order++;
|
||||
if (order > 2 && !last)
|
||||
{
|
||||
offset += 0.5f;
|
||||
order = 1;
|
||||
}
|
||||
if (last)
|
||||
offset += 0.6f;
|
||||
|
||||
return button.gameObject;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetValueText()
|
||||
{
|
||||
var value = GetValue();
|
||||
var formated = Helpers.FormatValue(value, SuffixType, FormatString);
|
||||
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}: <b>{formated}</font></b>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using BepInEx.Configuration;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract class LocalSettingBase<T> : ILocalSetting
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public string Name { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string Description { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public GameObject? Setting { get; } = null!;
|
||||
|
||||
/// <inheritdoc />
|
||||
public ConfigEntryBase ConfigEntry { get; }
|
||||
|
||||
internal LocalSettingsTab? Tab => LocalSettingsManager.Tabs.FirstOrDefault(x => x.Settings.Contains(this));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalSettingBase{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="tab">The tab to create the setting in.</param>
|
||||
/// <param name="configEntry">The config entry.</param>
|
||||
/// <param name="name">The name of the setting.</param>
|
||||
/// <param name="description">The description of the setting.</param>
|
||||
protected LocalSettingBase(Type tab, ConfigEntryBase configEntry, string? name = null, string? description = null)
|
||||
{
|
||||
ConfigEntry = configEntry;
|
||||
Name = name ?? ConfigEntry.Definition.Key;
|
||||
Description = description ?? ConfigEntry.Description.Description;
|
||||
LocalSettingsManager.TypeToTab[tab].Settings.Add(this);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of the setting.
|
||||
/// </summary>
|
||||
/// <returns>The created setting.</returns>
|
||||
public abstract GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the formated string to use in the text of the setting.
|
||||
/// </summary>
|
||||
/// <returns>The value text.</returns>
|
||||
protected virtual string GetValueText()
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the config entry, casted to the setting type.
|
||||
/// </summary>
|
||||
/// <returns>The value.</returns>
|
||||
public virtual T GetValue()
|
||||
{
|
||||
return (T)ConfigEntry.BoxedValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the value of the config entry.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to set to.</param>
|
||||
public virtual void SetValue(T value)
|
||||
{
|
||||
ConfigEntry.BoxedValue = value;
|
||||
Tab?.OnOptionChanged(ConfigEntry);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Local setting class for sliders.
|
||||
/// </summary>
|
||||
public class LocalSliderSetting : LocalSettingBase<float>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the range of the slider.
|
||||
/// </summary>
|
||||
public FloatRange SliderRange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the value should be displayed next to name.
|
||||
/// </summary>
|
||||
public bool DisplayValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a format for the text to use to format the number.
|
||||
/// </summary>
|
||||
public string FormatString { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the value should be rounded.
|
||||
/// </summary>
|
||||
public bool RoundValue { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suffix for the number value.
|
||||
/// </summary>
|
||||
public MiraNumberSuffixes SuffixType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalSliderSetting"/> class.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
/// <param name="sliderRange">The value range.</param>
|
||||
/// <param name="suffixType">The suffix used for formating.</param>
|
||||
/// <param name="formatString">The format string used for formating.</param>
|
||||
/// <param name="roundValue">Should the value be rounded.</param>
|
||||
public LocalSliderSetting(
|
||||
Type tab,
|
||||
ConfigEntryBase configEntry,
|
||||
string? name = null,
|
||||
string? description = null,
|
||||
FloatRange? sliderRange = null,
|
||||
bool displayValue = false,
|
||||
MiraNumberSuffixes? suffixType = null,
|
||||
string? formatString = null,
|
||||
bool roundValue = false)
|
||||
: base(tab, configEntry, name, description)
|
||||
{
|
||||
SliderRange = sliderRange ?? new FloatRange(0, 100);
|
||||
DisplayValue = displayValue;
|
||||
SuffixType = suffixType ?? MiraNumberSuffixes.None;
|
||||
FormatString = formatString ?? "0.0";
|
||||
RoundValue = roundValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
|
||||
{
|
||||
var newSlider = Object.Instantiate(slider, parent).GetComponent<SlideBar>();
|
||||
var rollover = newSlider.GetComponent<ButtonRolloverHandler>();
|
||||
newSlider.Title = newSlider.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>(); // Why the hell slider has a title property that is not even assigned???
|
||||
newSlider.Title.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
newSlider.gameObject.SetActive(true);
|
||||
newSlider.Bar.color = Tab!.TabAppearance.SliderColor;
|
||||
|
||||
if (order == 2)
|
||||
offset += 0.5f;
|
||||
|
||||
newSlider.Bar.transform.localPosition = new Vector3(2.85f, 0, 0);
|
||||
newSlider.transform.localPosition = new Vector3(-2.12f, 1.85f - offset, -7);
|
||||
newSlider.name = Name;
|
||||
newSlider.Range = new FloatRange(-1.5f, 1.5f);
|
||||
newSlider.SetValue(Mathf.InverseLerp(SliderRange.min, SliderRange.max, GetValue()));
|
||||
rollover.OutColor = Tab!.TabAppearance.SliderColor;
|
||||
rollover.OverColor = Tab!.TabAppearance.SliderHoverColor;
|
||||
newSlider.Title.transform.localPosition = new Vector3(0.5f, 0, -1f);
|
||||
newSlider.Title.horizontalAlignment = DisplayValue ? HorizontalAlignmentOptions.Left : HorizontalAlignmentOptions.Center;
|
||||
newSlider.Title.text = GetValueText();
|
||||
|
||||
newSlider.OnValueChange.AddListener((UnityAction)(() =>
|
||||
{
|
||||
SetValue(RoundValue
|
||||
? Mathf.Round(Mathf.Lerp(SliderRange.min, SliderRange.max, newSlider.Value))
|
||||
: Mathf.Lerp(SliderRange.min, SliderRange.max, newSlider.Value));
|
||||
|
||||
newSlider.Title.text = GetValueText();
|
||||
}));
|
||||
|
||||
order = 1;
|
||||
offset += 0.5f;
|
||||
return newSlider.gameObject;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override string GetValueText()
|
||||
{
|
||||
if (DisplayValue)
|
||||
{
|
||||
var value = GetValue();
|
||||
var formated = Helpers.FormatValue(value, SuffixType, FormatString);
|
||||
var maxFormated = Helpers.FormatValue(SliderRange.max, SuffixType, FormatString);
|
||||
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}: <b>{formated} / {maxFormated}</font></b>";
|
||||
}
|
||||
|
||||
return $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF - Chat Message Masked\">{Name}</font></b>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Localization.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.LocalSettings.SettingTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Local setting class for toggles.
|
||||
/// </summary>
|
||||
public class LocalToggleSetting : LocalSettingBase<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalToggleSetting"/> class.
|
||||
/// </summary>
|
||||
/// <inheritdoc/>
|
||||
public LocalToggleSetting(
|
||||
Type tab,
|
||||
ConfigEntryBase configEntry,
|
||||
string? name = null,
|
||||
string? description = null
|
||||
) : base(tab, configEntry, name, description)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override GameObject CreateOption(ToggleButtonBehaviour toggle, SlideBar slider, Transform parent, ref float offset, ref int order, bool last)
|
||||
{
|
||||
var toggleObject = Object.Instantiate(toggle, parent).GetComponent<ToggleButtonBehaviour>();
|
||||
var tmp = toggleObject.transform.FindChild("Text_TMP").GetComponent<TextMeshPro>();
|
||||
var passiveButton = toggleObject.GetComponent<PassiveButton>();
|
||||
var rollover = toggleObject.GetComponent<ButtonRolloverHandler>();
|
||||
toggleObject.gameObject.SetActive(true);
|
||||
|
||||
if (last && order == 1)
|
||||
{
|
||||
// Toggle in the middle
|
||||
toggleObject.transform.localPosition = new Vector3(0, 1.85f - offset, -7);
|
||||
}
|
||||
else
|
||||
{
|
||||
toggleObject.transform.localPosition = new Vector3(order == 1 ? -1.185f : 1.185f, 1.85f - offset, -7);
|
||||
}
|
||||
|
||||
toggleObject.BaseText = CustomStringName.CreateAndRegister(Name);
|
||||
toggleObject.UpdateText(GetValue());
|
||||
toggleObject.name = Name;
|
||||
toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor;
|
||||
passiveButton.OnClick = new UnityEngine.UI.Button.ButtonClickedEvent();
|
||||
rollover.OverColor = Tab!.TabAppearance.ToggleHoverColor;
|
||||
|
||||
passiveButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
SetValue(!GetValue());
|
||||
toggleObject.UpdateText(GetValue());
|
||||
toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor;
|
||||
}));
|
||||
passiveButton.OnMouseOver.AddListener((UnityAction)(() =>
|
||||
{
|
||||
if (!Description.IsNullOrWhiteSpace())
|
||||
{
|
||||
tmp.text = Description;
|
||||
}
|
||||
}));
|
||||
passiveButton.OnMouseOut.AddListener((UnityAction)(() =>
|
||||
{
|
||||
toggleObject.UpdateText(GetValue());
|
||||
toggleObject.Background.color = GetValue() ? Tab!.TabAppearance.ToggleActiveColor : Tab!.TabAppearance.ToggleInactiveColor;
|
||||
}));
|
||||
|
||||
Helpers.DivideSize(toggleObject.gameObject, 1.1f);
|
||||
|
||||
order++;
|
||||
if (order > 2 && !last)
|
||||
{
|
||||
offset += 0.5f;
|
||||
order = 1;
|
||||
}
|
||||
if (last)
|
||||
offset += 0.6f;
|
||||
|
||||
return toggleObject.gameObject;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@ namespace MiraAPI;
|
||||
[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
|
||||
{
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.LocalSettings.Attributes;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
/// <summary>
|
||||
/// Mira API Config File Handler
|
||||
/// </summary>
|
||||
public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string TabName => "Mira API";
|
||||
|
||||
/// <inheritdoc />
|
||||
public override LocalSettingTabAppearance TabAppearance => new()
|
||||
{
|
||||
TabButtonHoverColor = MiraApiPlugin.MiraColor,
|
||||
TabIcon = MiraAssets.SettingsIcon,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to show keybinds in the control mapper.
|
||||
/// </summary>
|
||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds in Control Mapper", true);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
||||
/// </summary>
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> PrettyTaskAdder { get; private set; } = config.Bind("Freeplay", "Pretty Task Laptop", true);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
@@ -38,8 +40,8 @@ public abstract class BaseModifier : IOptionable
|
||||
/// Gets the parent mod of the modifier.
|
||||
/// </summary>
|
||||
public MiraPluginInfo ParentMod => Array.Find(
|
||||
MiraPluginManager.Instance.RegisteredPlugins(),
|
||||
x => x.Modifiers.Exists(y => y.TypeId == TypeId)
|
||||
MiraPluginManager.Instance.RegisteredPlugins,
|
||||
x => x.InternalModifiers.Exists(y => y.TypeId == TypeId)
|
||||
) ?? throw new InvalidOperationException("Modifier is not registered.");
|
||||
|
||||
/// <summary>
|
||||
@@ -48,25 +50,35 @@ public abstract class BaseModifier : IOptionable
|
||||
public abstract string ModifierName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is hidden on the UI.
|
||||
/// Gets the modifier icon. Useless if HideOnUi is true.
|
||||
/// </summary>
|
||||
public virtual bool HideOnUi => false;
|
||||
public virtual LoadableAsset<Sprite>? ModifierIcon => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is hidden on the UI. Will be hidden either way if no description is provided.
|
||||
/// </summary>
|
||||
public virtual bool HideOnUi => GetDescription() == string.Empty;
|
||||
|
||||
/// <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 the color that should be used for the modifier within freeplay.
|
||||
/// </summary>
|
||||
public virtual Color FreeplayFileColor => Color.gray;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is unique. If true, the player can only have one instance of this modifier.
|
||||
/// </summary>
|
||||
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.
|
||||
/// Gets the HUD description for this modifier. Does nothing if <see cref="HideOnUi"/> is true. Required to be visible on UI.
|
||||
/// </summary>
|
||||
/// <returns>The information string for the HUD.</returns>
|
||||
public virtual string GetHudString() => ModifierName;
|
||||
/// <returns>The description string for the HUD.</returns>
|
||||
public virtual string GetDescription() => string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Called when the modifier is activated.
|
||||
@@ -104,6 +116,13 @@ public abstract class BaseModifier : IOptionable
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when a meeting starts.
|
||||
/// </summary>
|
||||
public virtual void OnMeetingStart()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the player can vent.
|
||||
/// </summary>
|
||||
|
||||
@@ -3,13 +3,13 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.Modifiers.ModifierDisplay;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Patches.Roles;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
@@ -26,13 +26,13 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
[HideFromIl2Cpp]
|
||||
public ImmutableList<BaseModifier> ActiveModifiers { get; private set; } = ImmutableList<BaseModifier>.Empty;
|
||||
|
||||
private ModifierDisplayComponent? ModifierDisplay { get; set; }
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
private List<BaseModifier> Modifiers { get; set; } = [];
|
||||
|
||||
private PlayerControl _player = null!;
|
||||
|
||||
private TextMeshPro _modifierText = null!;
|
||||
|
||||
private readonly List<BaseModifier> _toRemove = [];
|
||||
|
||||
private readonly List<BaseModifier> _toAdd = [];
|
||||
@@ -61,23 +61,42 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
return;
|
||||
}
|
||||
|
||||
_modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right);
|
||||
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
|
||||
ModifierDisplay = ModifierDisplayComponent.CreateDisplay();
|
||||
ModifierDisplay?.UpdateModifiersList(Modifiers);
|
||||
}
|
||||
|
||||
private void FixedUpdate()
|
||||
{
|
||||
var removed = _toRemove.Count > 0;
|
||||
foreach (var modifier in _toRemove.ToArray())
|
||||
{
|
||||
modifier.OnDeactivate();
|
||||
_toRemove.Remove(modifier);
|
||||
try
|
||||
{
|
||||
modifier.OnDeactivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
Modifiers.Remove(modifier);
|
||||
}
|
||||
|
||||
var added = _toAdd.Count > 0;
|
||||
foreach (var modifier in _toAdd.ToArray())
|
||||
{
|
||||
_toAdd.Remove(modifier);
|
||||
Modifiers.Add(modifier);
|
||||
modifier.Initialized = true;
|
||||
modifier.OnActivate();
|
||||
try
|
||||
{
|
||||
modifier.OnActivate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
if (modifier is TimedModifier { AutoStart: true } timer)
|
||||
{
|
||||
@@ -85,21 +104,42 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
}
|
||||
|
||||
if (_toAdd.Count > 0 || _toRemove.Count > 0)
|
||||
if (removed || added)
|
||||
{
|
||||
if (_player.AmOwner && HudManager.InstanceExists)
|
||||
{
|
||||
HudManager.Instance.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
ModifierDisplay?.UpdateModifiersList(Modifiers);
|
||||
}
|
||||
_toAdd.Clear();
|
||||
_toRemove.Clear();
|
||||
|
||||
ActiveModifiers = Modifiers.ToImmutableList();
|
||||
}
|
||||
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
try
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
|
||||
{
|
||||
var taskPanelOpen = HudManager.Instance.TaskPanel.open;
|
||||
var roleTabOpen = HudManagerPatches.RoleTab != null && HudManagerPatches.RoleTab.open;
|
||||
var leftSideHud = LocalSettingsTabSingleton<MiraApiSettings>.Instance.ModifiersHudLeftSide.Value;
|
||||
|
||||
if (leftSideHud && (taskPanelOpen || roleTabOpen))
|
||||
{
|
||||
ModifierDisplay!.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,34 +147,15 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
modifier.Update();
|
||||
}
|
||||
|
||||
if (!_modifierText || !_player.AmOwner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (MeetingHud.Instance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var showText = false;
|
||||
var builder = new StringBuilder("<b><size=130%>Modifiers:</b></size>\n");
|
||||
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
{
|
||||
if (modifier.HideOnUi)
|
||||
try
|
||||
{
|
||||
continue;
|
||||
modifier.Update();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
showText = true;
|
||||
builder.AppendLine(modifier.GetHudString());
|
||||
}
|
||||
|
||||
_modifierText.text = showText ? builder.ToString() : string.Empty;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
@@ -507,4 +528,61 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
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,225 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.Modifiers.ModifierDisplay;
|
||||
|
||||
/// <summary>
|
||||
/// The code used to display Mira modifiers.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the instance of the Modifier Display.
|
||||
/// </summary>
|
||||
public static ModifierDisplayComponent? Instance { get; private set; }
|
||||
|
||||
private RectTransform _children = null!;
|
||||
private GameObject _modTemplate = null!;
|
||||
private PassiveButton _toggleButton = null!;
|
||||
private TextMeshPro _toggleBtnText = null!;
|
||||
|
||||
private RectTransform _pagination = null!;
|
||||
private TextMeshPro _pageText = null!;
|
||||
private PassiveButton _nextButton = null!;
|
||||
private PassiveButton _backButton = null!;
|
||||
|
||||
private int _currentPage;
|
||||
private const int ItemsPerPage = 3;
|
||||
|
||||
private readonly Dictionary<BaseModifier, ModifierUiComponent> _modifiers = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets a read only dictionary of the created modifier UI components.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
public ReadOnlyDictionary<BaseModifier, ModifierUiComponent> Modifiers => new(_modifiers);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier display hud is currently open.
|
||||
/// </summary>
|
||||
public bool IsOpen { get; private set; }
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
_children = transform.GetChild(0).GetComponent<RectTransform>();
|
||||
_modTemplate = transform.GetChild(2).gameObject;
|
||||
|
||||
_toggleButton = transform.GetChild(1).GetComponent<PassiveButton>();
|
||||
_toggleButton.OnClick = new Button.ButtonClickedEvent();
|
||||
_toggleButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
IsOpen = !IsOpen;
|
||||
_children.gameObject.SetActive(!_children.gameObject.activeSelf);
|
||||
}));
|
||||
|
||||
_toggleButton.ClickSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().ClickSound;
|
||||
_toggleButton.HoverSound = HudManager.Instance.GameMenu.StreamerModeButton.GetComponent<PassiveButton>().HoverSound;
|
||||
_toggleButton.inactiveSprites = _toggleButton.transform.GetChild(1).gameObject;
|
||||
_toggleBtnText = _toggleButton.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
|
||||
_pagination = _children.transform.GetChild(0).GetComponent<RectTransform>();
|
||||
_pageText = _pagination.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
_nextButton = _pagination.transform.GetChild(1).GetComponent<PassiveButton>();
|
||||
_backButton = _pagination.transform.GetChild(2).GetComponent<PassiveButton>();
|
||||
|
||||
_toggleBtnText.font = _pageText.font = _nextButton.buttonText.font = _backButton.buttonText.font = HudManager.Instance.TaskPanel.taskText.font;
|
||||
_toggleBtnText.fontMaterial = _pageText.fontMaterial = _nextButton.buttonText.fontMaterial = _backButton.buttonText.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
|
||||
|
||||
_nextButton.OnClick = new Button.ButtonClickedEvent();
|
||||
_nextButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
_currentPage++;
|
||||
UpdatePage();
|
||||
}));
|
||||
|
||||
_backButton.OnClick = new Button.ButtonClickedEvent();
|
||||
_backButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
_currentPage--;
|
||||
UpdatePage();
|
||||
}));
|
||||
|
||||
IsOpen = false;
|
||||
_children.gameObject.SetActive(false);
|
||||
|
||||
if (LocalSettingsTabSingleton<MiraApiSettings>.Instance.ModifiersHudLeftSide.Value)
|
||||
{
|
||||
var aspect = GetComponent<AspectPosition>();
|
||||
_toggleButton.transform.localPosition = new Vector3(-1f, 2.7f, 0f);
|
||||
_children.transform.localPosition = new Vector3(-0.2f, -0.05f, 0f);
|
||||
aspect.Alignment = AspectPosition.EdgeAlignments.LeftTop;
|
||||
aspect.DistanceFromEdge = new Vector3(1.8f, 2.55f, -20f);
|
||||
aspect.AdjustPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to refresh the modifiers UI. Useful if you hide modifiers uis based on options.
|
||||
/// </summary>
|
||||
public void RefreshModifiers()
|
||||
{
|
||||
UpdateModifiersList([.. PlayerControl.LocalPlayer.GetModifierComponent().ActiveModifiers]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to toggle the visibility of the modifiers tab.
|
||||
/// </summary>
|
||||
public void ToggleTab()
|
||||
{
|
||||
SetOpened(!IsOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to set the visibility of the modifiers tab.
|
||||
/// </summary>
|
||||
/// <param name="val">Whether it should be visible or not.</param>
|
||||
public void SetOpened(bool val)
|
||||
{
|
||||
IsOpen = val;
|
||||
_children.gameObject.SetActive(IsOpen);
|
||||
RefreshModifiers();
|
||||
}
|
||||
|
||||
internal void DestroyComponent(ModifierUiComponent component)
|
||||
{
|
||||
_modifiers.Remove(component.Modifier!);
|
||||
component.gameObject.DestroyImmediate();
|
||||
RefreshModifiers();
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
internal static ModifierDisplayComponent CreateDisplay()
|
||||
{
|
||||
var gameObject = Instantiate(MiraAssets.ModifierDisplay.LoadAsset(), HudManager.Instance.transform);
|
||||
var minigame = gameObject.AddComponent<ModifierDisplayComponent>();
|
||||
return minigame;
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
private ModifierUiComponent CreateForModifier(BaseModifier modifier)
|
||||
{
|
||||
var newMod = Instantiate(_modTemplate, _children.transform);
|
||||
newMod.transform.name = modifier.ModifierName;
|
||||
var comp = newMod.AddComponent<ModifierUiComponent>();
|
||||
comp.Modifier = modifier;
|
||||
return comp;
|
||||
}
|
||||
|
||||
private void UpdatePage()
|
||||
{
|
||||
var totalPages = Mathf.CeilToInt((float)_modifiers.Count / ItemsPerPage);
|
||||
_currentPage = Mathf.Clamp(_currentPage, 0, Mathf.Max(0, totalPages - 1));
|
||||
|
||||
if (_modifiers.Count <= ItemsPerPage)
|
||||
{
|
||||
_modifiers.Do(x => x.Value.gameObject.SetActive(true));
|
||||
_pagination.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_pagination.gameObject.SetActive(true);
|
||||
if (_pageText != null)
|
||||
{
|
||||
_pageText.text = $"Page {_currentPage + 1}/{Mathf.Max(totalPages, 1)}";
|
||||
}
|
||||
|
||||
_nextButton.gameObject.SetActive(true);
|
||||
_backButton.gameObject.SetActive(true);
|
||||
|
||||
_modifiers.Do(x => x.Value.gameObject.SetActive(false));
|
||||
var start = _currentPage * ItemsPerPage;
|
||||
var end = Mathf.Min(start + ItemsPerPage, _modifiers.Count);
|
||||
var modifiers = _modifiers.ToArray();
|
||||
|
||||
for (var i = start; i < end; i++)
|
||||
{
|
||||
modifiers[i].Value.gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
_pagination.SetAsLastSibling();
|
||||
}
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
internal void UpdateModifiersList(List<BaseModifier> modifiers)
|
||||
{
|
||||
var filteredModifiers = modifiers.Where(x => !x.HideOnUi && x.GetDescription() != string.Empty).ToList();
|
||||
var modifiersToAdd = filteredModifiers.ToList();
|
||||
|
||||
foreach (var mod in _modifiers.ToArray())
|
||||
{
|
||||
if (modifiersToAdd.Contains(mod.Key))
|
||||
{
|
||||
modifiersToAdd.Remove(mod.Key);
|
||||
continue;
|
||||
}
|
||||
|
||||
mod.Value.gameObject.Destroy();
|
||||
_modifiers.Remove(mod.Key);
|
||||
}
|
||||
|
||||
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
|
||||
|
||||
_toggleButton.gameObject.SetActive(filteredModifiers.Count != 0);
|
||||
|
||||
foreach (var mod in modifiersToAdd)
|
||||
{
|
||||
_modifiers.Add(mod, CreateForModifier(mod));
|
||||
}
|
||||
|
||||
UpdatePage();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Modifiers.ModifierDisplay;
|
||||
|
||||
/// <summary>
|
||||
/// The code placed on every Modifier HUD object. Used to handle updating.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierUiComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the modifier which this component is for.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
public BaseModifier? Modifier { get; internal set; }
|
||||
|
||||
// ReSharper disable InconsistentNaming
|
||||
private SpriteRenderer modBg = null!;
|
||||
private TextMeshPro desc = null!;
|
||||
private SpriteRenderer icon = null!;
|
||||
private TextMeshPro nameText = null!;
|
||||
private RectTransform descRect = null!;
|
||||
// ReSharper restore InconsistentNaming
|
||||
private void Awake()
|
||||
{
|
||||
if (Modifier == null) return;
|
||||
|
||||
modBg = transform.GetChild(0).GetComponent<SpriteRenderer>();
|
||||
desc = modBg.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
icon = modBg.transform.GetChild(1).GetComponent<SpriteRenderer>();
|
||||
var nameBg = transform.GetChild(1);
|
||||
nameText = nameBg.GetChild(0).GetComponent<TextMeshPro>();
|
||||
descRect = desc.gameObject.GetComponent<RectTransform>();
|
||||
|
||||
nameText.font = desc.font = HudManager.Instance.TaskPanel.taskText.font;
|
||||
nameText.fontMaterial = desc.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
|
||||
}
|
||||
|
||||
// Might not be the best solution, but used to update name/description/icon if updated in the modifier.
|
||||
private void FixedUpdate()
|
||||
{
|
||||
if (Modifier == null || ModifierDisplayComponent.Instance == null || !ModifierDisplayComponent.Instance.IsOpen) return;
|
||||
|
||||
if (Modifier.HideOnUi || Modifier.GetDescription() == string.Empty)
|
||||
{
|
||||
ModifierDisplayComponent.Instance.DestroyComponent(this);
|
||||
return;
|
||||
}
|
||||
|
||||
nameText.text = Modifier.ModifierName;
|
||||
desc.text = Modifier.GetDescription();
|
||||
modBg.gameObject.SetActive(true);
|
||||
|
||||
icon.gameObject.SetActive(Modifier.ModifierIcon != null);
|
||||
if (icon.gameObject && icon.gameObject.activeSelf && Modifier.ModifierIcon != null)
|
||||
{
|
||||
icon.sprite = Modifier.ModifierIcon?.LoadAsset();
|
||||
icon.size = new Vector2(0.7f, 0.7f);
|
||||
icon.tileMode = SpriteTileMode.Continuous;
|
||||
descRect.localPosition = new Vector3(0.38f, -0.1f, -5);
|
||||
descRect.sizeDelta = new Vector2(2.38f, 0.65f);
|
||||
}
|
||||
else
|
||||
{
|
||||
descRect.localPosition = new Vector3(0, -0.1f, -5);
|
||||
descRect.sizeDelta = new Vector2(3.1f, 0.65f);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,7 +30,7 @@ public static class ModifierExtensions
|
||||
_ = ModifierManager.GetModifierType(typeId) ?? throw new InvalidOperationException(
|
||||
$"Modifier with ID {typeId} is not registered.");
|
||||
|
||||
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args));
|
||||
Rpc<AddModifierRpc>.Instance.Send(target, new ModifierData(typeId, Guid.NewGuid(), args), true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -180,6 +180,17 @@ public static class ModifierExtensions
|
||||
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>
|
||||
@@ -204,17 +215,6 @@ public static class ModifierExtensions
|
||||
}
|
||||
}
|
||||
|
||||
/// <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>
|
||||
/// Tries to get a modifier by its type.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -11,7 +12,7 @@ namespace MiraAPI.Modifiers;
|
||||
/// </summary>
|
||||
public static class ModifierFactory
|
||||
{
|
||||
private static readonly Dictionary<(Type, Type[]), Func<object[], BaseModifier>> _constructorCache = [];
|
||||
private static readonly Dictionary<(Type, Type[]), Func<object[], BaseModifier>> ConstructorCache = [];
|
||||
|
||||
private static Func<object[], BaseModifier> CreateConstructor(Type type, params object[] args)
|
||||
{
|
||||
@@ -45,13 +46,13 @@ public static class ModifierFactory
|
||||
/// <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 argTypes = args.Select(arg => arg.GetType()).ToArray();
|
||||
var key = (type, argTypes);
|
||||
|
||||
if (!_constructorCache.TryGetValue(key, out var constructor))
|
||||
if (!ConstructorCache.TryGetValue(key, out var constructor))
|
||||
{
|
||||
constructor = CreateConstructor(type, args);
|
||||
_constructorCache[key] = constructor;
|
||||
ConstructorCache[key] = constructor;
|
||||
}
|
||||
|
||||
return constructor(args);
|
||||
@@ -95,6 +96,7 @@ public static class ModifierFactory<T> where T : BaseModifier
|
||||
/// </summary>
|
||||
/// <param name="args">Parameters for the constructor.</param>
|
||||
/// <returns>An instance of the modifier.</returns>
|
||||
[SuppressMessage("Design", "CA1000:Do not declare static members on generic types", Justification = "This is a factory class.")]
|
||||
public static T CreateInstance(params object[] args)
|
||||
{
|
||||
return Constructor(args);
|
||||
|
||||
@@ -17,6 +17,18 @@ namespace MiraAPI.Modifiers;
|
||||
/// </summary>
|
||||
public static class ModifierManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether modifiers should be assigned to players.
|
||||
/// </summary>
|
||||
public static bool MiraAssignsModifiers { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all registered modifiers.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<BaseModifier> Modifiers { get; internal set; } = [];
|
||||
|
||||
internal static List<BaseModifier> InternalModifiers { get; } = [];
|
||||
|
||||
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
|
||||
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
|
||||
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
|
||||
@@ -75,7 +87,8 @@ public static class ModifierManager
|
||||
modifier = (BaseModifier)FormatterServices.GetUninitializedObject(modifierType);
|
||||
}
|
||||
|
||||
info.Modifiers.Add(modifier);
|
||||
info.InternalModifiers.Add(modifier);
|
||||
InternalModifiers.Add(modifier);
|
||||
|
||||
if (modifier is not GameModifier gameModifier)
|
||||
{
|
||||
@@ -99,72 +112,66 @@ public static class ModifierManager
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void AssignModifiers(List<PlayerControl> plrs)
|
||||
/// <summary>
|
||||
/// Assigns modifiers to players. ONLY CALL THIS METHOD IF YOU KNOW WHAT YOU ARE DOING.
|
||||
/// </summary>
|
||||
/// <param name="plrs">The players to assign modifiers to.</param>
|
||||
public static void AssignModifiers(List<PlayerControl> plrs)
|
||||
{
|
||||
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)
|
||||
.Shuffle()
|
||||
.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);
|
||||
}
|
||||
|
||||
foreach (var id in shuffledList)
|
||||
{
|
||||
var mod = Activator.CreateInstance(IdToTypeModifierMap[id]) as GameModifier;
|
||||
var plr = plrs.Where(x => IsGameModifierValid(x, mod!, id)).Random();
|
||||
|
||||
if (!plr || plr == null)
|
||||
var chance = Math.Clamp(modifier.GetAssignmentChance(), 0, 100);
|
||||
if (rand.Next(100) >= chance)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared");
|
||||
continue;
|
||||
}
|
||||
else
|
||||
|
||||
var candidates = availablePlayers
|
||||
.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId))
|
||||
.ToList();
|
||||
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
plr.RpcAddModifier(id);
|
||||
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($"Valid player for modifier {modifier.ModifierName} disappeared");
|
||||
continue;
|
||||
}
|
||||
|
||||
plr.RpcAddModifier(modifier.TypeId);
|
||||
availablePlayers.Remove(plr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class GameModifier : BaseModifier
|
||||
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the player won the game with this modifier.
|
||||
/// Determines whether the player won the game with this modifier. The modifier with the highest priority will override other modifiers.
|
||||
/// </summary>
|
||||
/// <param name="reason">The reason why the game ended.</param>
|
||||
/// <returns>True if the player won, false if they lost. Return null to use the player's default win condition.</returns>
|
||||
|
||||
@@ -45,12 +45,12 @@ public abstract class TimedModifier : BaseModifier
|
||||
public override bool HideOnUi => !TimerActive;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the HUD information for Timed Modifier, including the time remaining.
|
||||
/// Gets the HUD description for Timed Modifier, including the time remaining.
|
||||
/// </summary>
|
||||
/// <returns>A string with the hud information + timer.</returns>
|
||||
public override string GetHudString()
|
||||
/// <returns>A string with the time remaining.</returns>
|
||||
public override string GetDescription()
|
||||
{
|
||||
return base.GetHudString() + $" <size=70%>({Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s)</size>";
|
||||
return $"Remaining Time: {Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using AmongUs.Data;
|
||||
using AmongUs.GameOptions;
|
||||
using Assets.CoreScripts;
|
||||
using BepInEx.Unity.IL2CPP.Utils;
|
||||
@@ -28,7 +29,7 @@ public static class CustomMurderRpc
|
||||
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
|
||||
/// <param name="showKillAnim">Should the kill animation be shown.</param>
|
||||
/// <param name="playKillSound">Should the kill sound be played.</param>
|
||||
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before, SendImmediately = true)]
|
||||
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before)]
|
||||
public static void RpcCustomMurder(
|
||||
this PlayerControl source,
|
||||
PlayerControl target,
|
||||
@@ -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
|
||||
@@ -221,7 +222,7 @@ public static class CustomMurderRpc
|
||||
|
||||
if (createDeadBody)
|
||||
{
|
||||
deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab);
|
||||
deadBody = Object.Instantiate(GameManager.Instance.deadBodyPrefab[0]);
|
||||
deadBody.enabled = false;
|
||||
deadBody.ParentId = target.PlayerId;
|
||||
deadBody.bodyRenderers.ToList().ForEach(target.SetPlayerMaterialColors);
|
||||
@@ -263,7 +264,7 @@ public static class CustomMurderRpc
|
||||
deadBody.enabled = true;
|
||||
}
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target);
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target, deadBody);
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
|
||||
if (!isParticipant)
|
||||
|
||||
@@ -44,4 +44,9 @@ public enum MiraRpc : uint
|
||||
/// Custom RPC to undo a player's vote.
|
||||
/// </summary>
|
||||
RemoveVote,
|
||||
|
||||
/// <summary>
|
||||
/// Custom RPC to replace Among Us CastVote.
|
||||
/// </summary>
|
||||
CastVote,
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
[*.{cs,vb}]
|
||||
dotnet_diagnostic.CA1707.severity = none
|
||||
dotnet_diagnostic.CS1591.severity = none
|
||||
dotnet_diagnostic.SA1611.severity = none
|
||||
dotnet_diagnostic.SA1615.severity = none
|
||||
@@ -0,0 +1,35 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
#pragma warning disable SA1629
|
||||
/// <summary>
|
||||
/// Allows players to paste text into chat.
|
||||
/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs
|
||||
/// </summary>
|
||||
#pragma warning restore SA1629
|
||||
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))]
|
||||
public static class ChatControllerPatch
|
||||
{
|
||||
public static void Prefix(ChatController __instance)
|
||||
{
|
||||
if (!HudManager.Instance.Chat.IsOpenOrOpening) return;
|
||||
|
||||
var ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
||||
|
||||
if (!ctrlPressed || !Input.GetKeyDown(KeyCode.V)) return;
|
||||
|
||||
var clipboard = GUIUtility.systemCopyBuffer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clipboard)) return;
|
||||
clipboard = clipboard.Replace("<", string.Empty)
|
||||
.Replace(">", string.Empty)
|
||||
.Replace("\r", string.Empty);
|
||||
|
||||
if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
|
||||
clipboard = clipboard.Replace("\n", string.Empty);
|
||||
|
||||
__instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + clipboard);
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,16 @@ public static class CanUsePatches
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
yield return AccessTools.Method(typeof(Console), nameof(Console.CanUse));
|
||||
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
|
||||
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(DeconControl), nameof(DeconControl.CanUse));
|
||||
yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
|
||||
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.CanUse));
|
||||
// vent is not necessary as it has its own patch
|
||||
}
|
||||
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
|
||||
@@ -5,29 +5,44 @@ using MiraAPI.GameEnd;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
|
||||
internal static class EndGamePatches
|
||||
{
|
||||
public static bool Prefix(EndGameManager __instance)
|
||||
[HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
|
||||
public static class GameManagerPatches
|
||||
{
|
||||
if (CustomGameOver.Instance is not { } gameOver)
|
||||
public static bool Prefix(GameOverReason endReason)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
var @event = new BeforeGameEndEvent(endReason);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
var result = gameOver.BeforeEndGameSetup(__instance);
|
||||
return result;
|
||||
return !@event.IsCancelled;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Postfix(EndGameManager __instance)
|
||||
[HarmonyPatch(typeof(EndGameManager), nameof(EndGameManager.SetEverythingUp))]
|
||||
public static class EndGameManagerPatches
|
||||
{
|
||||
if (CustomGameOver.Instance is { } gameOver)
|
||||
public static bool Prefix(EndGameManager __instance)
|
||||
{
|
||||
gameOver.AfterEndGameSetup(__instance);
|
||||
CustomGameOver.Instance = null;
|
||||
if (CustomGameOver.Instance is not { } gameOver)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var result = gameOver.BeforeEndGameSetup(__instance);
|
||||
return result;
|
||||
}
|
||||
|
||||
var @event = new GameEndEvent(__instance);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch(typeof(TutorialManager._RunTutorial_d__3), nameof(TutorialManager._RunTutorial_d__3.MoveNext))]
|
||||
public static class FreeplayRoundStartPatch
|
||||
{
|
||||
public static void Postfix(TutorialManager._RunTutorial_d__3 __instance, ref bool __result)
|
||||
{
|
||||
if (!__result)
|
||||
{
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
@@ -13,13 +14,17 @@ public static class KillAnimationPatches
|
||||
/// <summary>
|
||||
/// Used to trigger the <see cref="AfterMurderEvent"/>.
|
||||
/// </summary>
|
||||
/// <param name="source">The killer.</param>
|
||||
/// <param name="target">The target.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(KillAnimation), nameof(KillAnimation.CoPerformKill))]
|
||||
public static void KillAnimPostfix(PlayerControl source, PlayerControl target)
|
||||
[HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
|
||||
public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
|
||||
{
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target);
|
||||
// Checking if state is -1 to wait for the end of coroutine.
|
||||
if (__instance.__1__state != -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId));
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,52 @@
|
||||
using HarmonyLib;
|
||||
using AmongUs.InnerNet.GameDataMessages;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Meeting;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class ReportBodyEventPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.CmdReportDeadBody))]
|
||||
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
|
||||
private static bool ReportBodyEventHook(PlayerControl source, NetworkedPlayerInfo? target)
|
||||
{
|
||||
var body = target != null ? Helpers.GetBodyById(target.PlayerId) : null;
|
||||
|
||||
var @event = new ReportBodyEvent(__instance, target, body);
|
||||
var @event = new ReportBodyEvent(source, target, body);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
return !@event.IsCancelled;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.ReportDeadBody))]
|
||||
public static bool PlayerControlCmdReportDeadBodyPrefix(PlayerControl __instance, NetworkedPlayerInfo? target)
|
||||
{
|
||||
return ReportBodyEventHook(__instance, target);
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(InnerNetClient), nameof(InnerNetClient.LateBroadcastReliableMessage))]
|
||||
public static bool LateBroadcastReliableMessagePrefix(IGameDataMessage rpcMessage)
|
||||
{
|
||||
var reportBodyMessage = rpcMessage.TryCast<RpcReportDeadBodyMessage>();
|
||||
|
||||
if (reportBodyMessage == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var source = AmongUsClient.Instance.FindObjectByNetId<PlayerControl>(reportBodyMessage.rpcObjectNetId);
|
||||
if (source == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
|
||||
return true;
|
||||
}
|
||||
|
||||
var target = GameData.Instance.GetPlayerById(reportBodyMessage.targetPlayerId);
|
||||
|
||||
return ReportBodyEventHook(source, target);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
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 BeforeRoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
|
||||
var @event2 = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event2);
|
||||
}
|
||||
}
|
||||
Loaded 100 of 173 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user