Compare commits

..
1 Commits
Author SHA1 Message Date
ang-xd 0b6d6209b0 Begin teams ig 2024-08-24 12:15:41 -04:00
176 changed files with 1764 additions and 7835 deletions

No files matched your search

-2
View File
@@ -1,2 +0,0 @@
[*.{cs,vb}]
dotnet_diagnostic.CA1707.severity = none
-51
View File
@@ -1,51 +0,0 @@
name: CI
on: ["workflow_dispatch", "push", "pull_request"]
jobs:
build:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
submodules: true
- uses: actions/cache@v4
with:
path: |
~/.nuget/packages
~/.cache/bepinex
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
restore-keys: |
${{ runner.os }}-nuget-
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Run the Cake script
uses: cake-build/cake-action@v2
with:
verbosity: Diagnostic
- uses: actions/upload-artifact@v4
with:
name: MiraAPI.dll
path: MiraAPI/bin/Release/net6.0/MiraAPI.dll
- uses: actions/upload-artifact@v4
with:
name: AllOfUs.MiraAPI.nupkg
path: MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg
- uses: softprops/action-gh-release@v2
if: github.ref_type == 'tag'
with:
draft: true
files: MiraAPI/bin/Release/net6.0/MiraAPI.dll
- name: Push NuGet package
if: github.ref_type == 'tag'
run: |
dotnet nuget push MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg --source https://api.nuget.org/v3/index.json --api-key ${{ secrets.NUGET_API_KEY }}
+1 -2
View File
@@ -4,5 +4,4 @@
obj/
bin/
*.user
*.lock.json
*.user
-14
View File
@@ -1,14 +0,0 @@
<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.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.0.1" PrivateAssets="all" ExcludeAssets="runtime" />
</ItemGroup>
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" UseSymboliclinksIfPossible="true" />
</Target>
</Project>
-45
View File
@@ -1,45 +0,0 @@
<Project>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<DebugType>embedded</DebugType>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.1.9</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Authors>All Of Us, XtraCube, Angxl</Authors>
<Company>All Of Us</Company>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/All-Of-Us-Mods/MiraAPI</RepositoryUrl>
<PackageLicenseExpression>LGPL-2.1-or-later</PackageLicenseExpression>
<!-- SourceLink -->
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSourceRevisionInInformationalVersion>false</IncludeSourceRevisionInInformationalVersion>
<ContinuousIntegrationBuild Condition="'$(GITHUB_ACTIONS)' == 'true'">true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>latest</AnalysisLevel>
<AnalysisMode>recommended</AnalysisMode>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<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">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)\stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
+26
View File
@@ -0,0 +1,26 @@
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
[RegisterButton]
public class ExampleButton : CustomActionButton
{
public override string Name => "Example Button";
public override float Cooldown => 5f;
public override float EffectDuration => 0f;
public override int MaxUses => 5;
public override LoadableAsset<Sprite> Sprite { get; } = ExampleAssets.ExampleButton;
protected override void OnClick()
{
Logger<ExamplePlugin>.Info("Example button clicked!");
}
public override bool Enabled(RoleBehaviour role)
{
return true;
}
}
@@ -1,46 +0,0 @@
using MiraAPI.Example.Modifiers.Freezer;
using MiraAPI.Example.Options.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Buttons.Freezer;
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 LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
protected override void OnClick()
{
Target?.RpcAddModifier<FreezeModifier>();
}
public override PlayerControl? GetTarget()
{
return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance);
}
public override void SetOutline(bool active)
{
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue));
}
public override bool IsTargetValid(PlayerControl? target)
{
return true;
}
public override bool Enabled(RoleBehaviour? role)
{
return role is FreezerRole;
}
}
-45
View File
@@ -1,45 +0,0 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
public class MeetingButton : CustomActionButton
{
public override string Name => "Call Meeting";
public override float Cooldown => 15;
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override bool Enabled(RoleBehaviour? role)
{
return PlayerControl.LocalPlayer != null && PlayerControl.LocalPlayer.HasModifier<CaptainModifier>();
}
protected override void OnClick()
{
var bt = ShipStatus.Instance.EmergencyButton;
PlayerControl.LocalPlayer.NetTransform.Halt();
var minigame = Object.Instantiate(bt.MinigamePrefab, Camera.main!.transform, false);
var taskAdderGame = minigame as TaskAdderGame;
if (taskAdderGame != null)
{
taskAdderGame.SafePositionWorld = bt.SafePositionLocal + (Vector2)bt.transform.position;
}
minigame.transform.localPosition = new Vector3(0f, 0f, -50f);
minigame.Begin(null);
if (UsesLeft == 0 && PlayerControl.LocalPlayer.HasModifier<CaptainModifier>())
{
PlayerControl.LocalPlayer.RpcRemoveModifier<CaptainModifier>();
}
}
}
@@ -1,87 +0,0 @@
using MiraAPI.Example.Options.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using System.Collections;
using UnityEngine;
namespace MiraAPI.Example.Buttons.Teleporter;
public class TeleportButton : CustomActionButton
{
public override string Name => "Teleport";
public override float Cooldown => OptionGroupSingleton<TeleporterOptions>.Instance.TeleportCooldown.Value;
public override float EffectDuration => OptionGroupSingleton<TeleporterOptions>.Instance.TeleportDuration;
public override int MaxUses => 2;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
public static bool IsZoom { get; private set; }
public override bool Enabled(RoleBehaviour? role)
{
return role is TeleporterRole;
}
protected override void OnClick()
{
Coroutines.Start(ZoomOutCoroutine());
}
public override void OnEffectEnd()
{
Coroutines.Start(ZoomInCoroutine());
}
protected override void FixedUpdate(PlayerControl playerControl)
{
base.FixedUpdate(playerControl);
if (!EffectActive) return;
if (Input.GetKey(KeyCode.Mouse0))
{
playerControl.NetTransform.RpcSnapTo(Camera.main.ScreenToWorldPoint(Input.mousePosition));
ResetCooldownAndOrEffect();
}
}
private static IEnumerator ZoomOutCoroutine()
{
HudManager.Instance.ShadowQuad.gameObject.SetActive(false);
IsZoom = true;
var zoomDistance = OptionGroupSingleton<TeleporterOptions>.Instance.ZoomDistance;
for (var ft = Camera.main!.orthographicSize; ft < zoomDistance; ft += 0.3f)
{
Camera.main.orthographicSize = MeetingHud.Instance ? 3f : ft;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
foreach (var cam in Camera.allCameras) cam.orthographicSize = Camera.main.orthographicSize;
yield return null;
}
foreach (var cam in Camera.allCameras) cam.orthographicSize = zoomDistance;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
}
private static IEnumerator ZoomInCoroutine()
{
for (var ft = Camera.main!.orthographicSize; ft > 3f; ft -= 0.3f)
{
Camera.main.orthographicSize = MeetingHud.Instance ? 3f : ft;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
foreach (var cam in Camera.allCameras) cam.orthographicSize = Camera.main.orthographicSize;
yield return null;
}
foreach (var cam in Camera.allCameras) cam.orthographicSize = 3f;
HudManager.Instance.ShadowQuad.gameObject.SetActive(true);
IsZoom = false;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
}
}
+1 -4
View File
@@ -5,8 +5,5 @@ namespace MiraAPI.Example;
public static class ExampleAssets
{
public static LoadableResourceAsset ExampleButton { get; } = new("MiraAPI.Example.Resources.ExampleButton.png");
// Credit to EpicHorrors for the teleport button asset.
public static LoadableResourceAsset TeleportButton { get; } = new("MiraAPI.Example.Resources.TeleportButton.png");
public static LoadableResourceAsset Banner { get; } = new("MiraAPI.Example.Resources.FortniteBanner.jpeg");
}
}
-14
View File
@@ -1,14 +0,0 @@
using MiraAPI.Colors;
using UnityEngine;
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 Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f));
public static CustomColor Gold { get; } = new("Gold", new Color(1.0f, 0.84f, 0.0f));
}
-59
View File
@@ -1,59 +0,0 @@
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Events.Vanilla.Gameplay;
using MiraAPI.Events.Vanilla.Map;
using MiraAPI.Events.Vanilla.Player;
using MiraAPI.Example.Buttons.Freezer;
using Reactor.Utilities;
namespace MiraAPI.Example;
public static class ExampleEventHandlers
{
public static void Initialize()
{
// Register event handlers here
MiraEventManager.RegisterEventHandler<MiraButtonClickEvent<FreezeButton>>(FreezeButtonClickHandler, 1);
MiraEventManager.RegisterEventHandler<MiraButtonCancelledEvent<FreezeButton>>(FreezeButtonCancelledHandler);
MiraEventManager.RegisterEventHandler<UpdateSystemEvent>(UpdateSystemEventHandler);
MiraEventManager.RegisterEventHandler<BeforeMurderEvent>(@event =>
{
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} is about to kill {@event.Target.Data.PlayerName}");
});
MiraEventManager.RegisterEventHandler<AfterMurderEvent>(@event =>
{
Logger<ExamplePlugin>.Info($"{@event.Source.Data.PlayerName} has killed {@event.Target.Data.PlayerName}");
});
MiraEventManager.RegisterEventHandler<CompleteTaskEvent>(@event =>
{
Logger<ExamplePlugin>.Info($"{@event.Player.Data.PlayerName} completed {@event.Task.TaskType.ToString()}");
});
}
public static void UpdateSystemEventHandler(UpdateSystemEvent @event)
{
Logger<ExamplePlugin>.Error(@event.SystemType.ToString());
}
// Example event handler
public static void FreezeButtonClickHandler(MiraButtonClickEvent<FreezeButton> @event)
{
Logger<ExamplePlugin>.Warning("Freeze button clicked!");
if (PlayerControl.LocalPlayer.Data.PlayerName == "stupid")
{
@event.Cancel();
@event.Button.SetTimer(15f);
}
}
// Example event handler
public static void FreezeButtonCancelledHandler(MiraButtonCancelledEvent<FreezeButton> @event)
{
Logger<ExamplePlugin>.Warning("Freeze button cancelled!");
@event.Button.OverrideName("Freeze Canceled");
}
}
+2 -4
View File
@@ -9,7 +9,7 @@ using Reactor.Networking.Attributes;
namespace MiraAPI.Example;
[BepInAutoPlugin("mira.example", "MiraExampleMod")]
[BepInAutoPlugin("mira.example", "MiraAPIExample")]
[BepInProcess("Among Us.exe")]
[BepInDependency(ReactorPlugin.Id)]
[BepInDependency(MiraApiPlugin.Id)]
@@ -17,11 +17,9 @@ namespace MiraAPI.Example;
public partial class ExamplePlugin : BasePlugin, IMiraPlugin
{
public Harmony Harmony { get; } = new(Id);
public string OptionsTitleText => "Mira API\nExample Mod";
public ConfigFile GetConfigFile() => Config;
public override void Load()
{
ExampleEventHandlers.Initialize();
Harmony.PatchAll();
}
}
}
+22 -4
View File
@@ -1,9 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.0.0</Version>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<DebugType>embedded</DebugType>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Description>Example mod for Mira API</Description>
<Authors>Angxl, XtraCube</Authors>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.0" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="Iced" Version="1.21.0" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.8.13" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
@@ -12,8 +29,9 @@
<ItemGroup>
<EmbeddedResource Include="Resources\ExampleButton.png" />
<EmbeddedResource Include="Resources\FortniteBanner.jpeg" />
<EmbeddedResource Include="Resources\TeleportButton.png" />
</ItemGroup>
<Import Project="..\AmongUs.props" />
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" UseSymboliclinksIfPossible="true" />
</Target>
</Project>
-27
View File
@@ -1,27 +0,0 @@
using System;
using Il2CppInterop.Runtime.Attributes;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using UnityEngine;
namespace MiraAPI.Example;
[RegisterInIl2Cpp]
public class MiraDebugWindow(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
[HideFromIl2Cpp]
public DragWindow DebuggingWindow { get; } = new(
new Rect(10, 10, 0, 0),
"MIRA API DEBUGGING",
() =>
{
})
{
Enabled = true,
};
public void OnGUI()
{
DebuggingWindow.OnGUI();
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace MiraAPI.Example;
public class ModifierExample
{
//name, role specific, etc
// mono behaviour stuff here (ModdedPlayerModifier inherits monobehaviour)
}
@@ -1,19 +0,0 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Captain";
public override int GetAmountPerGame()
{
return 1;
}
public override int GetAssignmentChance()
{
return 100;
}
}
@@ -1,42 +0,0 @@
using Il2CppSystem;
using MiraAPI.Example.Roles;
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
using UnityEngine;
namespace MiraAPI.Example.Modifiers.Freezer;
public class FreezeModifier : TimedModifier
{
public override string ModifierName => "Freezed";
public override bool HideOnUi => false;
public override float Duration => 15f;
public override bool AutoStart => true;
public override bool RemoveOnComplete => true;
public override void OnActivate()
{
if (Player?.AmOwner == true)
{
Player.moveable = false;
}
}
public override void FixedUpdate()
{
base.FixedUpdate();
if (Player?.AmOwner == true || PlayerControl.LocalPlayer.Data.Role is FreezerRole)
{
Player?.cosmetics.SetOutline(true, new Nullable<Color>(Palette.LightBlue));
}
}
public override void OnTimerComplete()
{
if (Player?.AmOwner == true)
{
Player.moveable = true;
}
}
}
@@ -1,23 +0,0 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
public class HighPriorityModifier : GameModifier
{
public override string ModifierName => "High Priority";
public override int GetAssignmentChance()
{
return 100;
}
public override int GetAmountPerGame()
{
return 1;
}
public override int Priority()
{
return 100;
}
}
+18 -16
View File
@@ -1,31 +1,33 @@
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Options;
public class ExampleOptions : AbstractOptionGroup
{
public override string GroupName => "Example Options 1";
public override Color GroupColor => Color.green;
public override string GroupName => "General Group";
public override Color GroupColor => Color.red;
[ModdedToggleOption("Toggle Opt 1")]
public bool ToggleOpt { get; set; } = false;
[ModdedToggleOption("Use Thing")]
public bool UseThing { get; set; } = false;
[ModdedToggleOption("Toggle Opt 2")]
public bool ToggleOpt2 { get; set; } = true;
[ModdedToggleOption("Use another thing")]
public bool UseAnotherThing { get; set; } = true;
[ModdedNumberOption("Number Opt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Percent)]
public float NumberOpt { get; set; } = 4f;
[ModdedNumberOption("Sussy level", min: 0, max: 10)]
public float SussyLevel { get; set; } = 4f;
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
public BestApi Opt { get; set; } = BestApi.MiraAPI;
[ModdedStringOption("Sus choices", ["hello", "hello 2", "hello 3"])]
public int SusChoices { get; set; } = 2;
[ModdedEnumOption("Enum test", typeof(TestEnum))]
public TestEnum EnumTest { get; set; } = TestEnum.Bye;
}
public enum BestApi
public enum TestEnum
{
MiraAPI,
Mitochondria,
Reactor,
}
Hello,
Hi,
Bye
}
+17 -21
View File
@@ -1,31 +1,27 @@
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.OptionTypes;
using Reactor.Utilities;
namespace MiraAPI.Example.Options;
public class ExampleOptions2 : AbstractOptionGroup
{
public override string GroupName => "Example Options 2";
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false);
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
{
Visible = () => OptionGroupSingleton<ExampleOptions2>.Instance.ToggleOpt1, // implicit cast from ModdedToggleOption to bool
};
public ModdedEnumOption<TestingData> EnumOpt { get; } = new("Enum Opt", 0)
{
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
};
public override string GroupName => "Example 2";
public ModdedToggleOption UseThing { get; } = new("Use Thingy 5", false);
public ModdedToggleOption UseThing2 { get; } = new("Use Thingy 6", false);
public ModdedStringOption Sussy { get; } = new("Sussy", 0, ["Sussy 1", "Sussy 2", "Sussy 3"]);
public ModdedEnumOption SussyEnum { get; } = new("Sussy Enum", 0, typeof(SussyEnum));
public ModdedNumberOption YeezyLevel { get; } = new("Yeezy Level", 50, 0, 100, 25, NumberSuffixes.Multiplier);
}
public enum TestingData : ulong
public enum SussyEnum
{
Happy,
Sad,
Neutral,
}
Sussy,
Baka,
Imposter,
Kanye
}
@@ -0,0 +1,16 @@
using System;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
namespace MiraAPI.Example.Options.Roles;
public class CustomRole2Settings : AbstractOptionGroup
{
public override string GroupName => "Custom Role";
public override Type AdvancedRole => typeof(CustomRole2);
[ModdedToggleOption("Teleportation ability")] public bool Teleport { get; set; } = true;
}
@@ -0,0 +1,16 @@
using System;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
namespace MiraAPI.Example.Options.Roles;
public class CustomRoleSettings : AbstractOptionGroup
{
public override string GroupName => "Custom Role";
public override Type AdvancedRole => typeof(CustomRole);
[ModdedNumberOption("Testing level", min: 3, max: 9)] public float TestingLevel { get; set; } = 4;
}
@@ -1,20 +0,0 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using System;
using MiraAPI.Utilities;
namespace MiraAPI.Example.Options.Roles;
public class FreezerRoleSettings : AbstractOptionGroup
{
public override string GroupName => "Custom Role";
public override Type AdvancedRole => typeof(FreezerRole);
[ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
public float FreezeDuration { get; set; } = 5;
[ModdedNumberOption("Freeze Uses", 1, 5)]
public float FreezeUses { get; set; } = 1;
}
@@ -1,23 +0,0 @@
using System;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Utilities;
namespace MiraAPI.Example.Options.Roles;
public class TeleporterOptions : AbstractOptionGroup
{
public override string GroupName => "Teleporter";
public override Type AdvancedRole => typeof(TeleporterRole);
public ModdedNumberOption TeleportCooldown { get; set; } = new("Teleport Cooldown", 10, 5, 60, 2.5f, MiraNumberSuffixes.Seconds);
[ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)]
public float TeleportDuration { get; set; } = 10;
[ModdedNumberOption("Zoom Distance", 4, 15)]
public float ZoomDistance { get; set; } = 6;
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

-47
View File
@@ -1,47 +0,0 @@
using MiraAPI.Roles;
using TMPro;
using UnityEngine;
namespace MiraAPI.Example.Roles;
public class ChameloenRole : CrewmateRole, ICustomRole
{
public string RoleName => "Chamelon";
public string RoleLongDescription => "Stay invisible while not moving.";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
{
OptionsScreenshot = ExampleAssets.Banner,
};
public void PlayerControlFixedUpdate(PlayerControl playerControl)
{
if (playerControl.MyPhysics.Velocity.magnitude > 0)
{
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, 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>())
{
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);
foreach (var cosmetic in playerControl.cosmetics.transform.GetComponentsInChildren<SpriteRenderer>())
{
cosmetic.color = Color.Lerp(cosmetic.color, new Color(1, 1, 1, playerControl.AmOwner ? 0.3f : 0), Time.deltaTime * 4f);
}
}
}
}
+17
View File
@@ -0,0 +1,17 @@
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole : ImpostorRole, ICustomRole
{
public string RoleName => "fortnite killer";
public string RoleLongDescription => "ok so your objective is to eliminate everyone with your fortnite tactical shotgun, good luck";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.Black;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
}
+17
View File
@@ -0,0 +1,17 @@
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole2 : CrewmateRole, ICustomRole
{
public string RoleName => "fortnite defender";
public string RoleLongDescription => "ok so your objective is to save everyone from the fortnite killer";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
}
+17
View File
@@ -0,0 +1,17 @@
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole3 : CrewmateRole, ICustomRole
{
public string RoleName => "fortnite helper";
public string RoleLongDescription => "ok so your objective is to help";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.Brown;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
}
-19
View File
@@ -1,19 +0,0 @@
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Example.Roles;
public class FreezerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Freezer";
public string RoleLongDescription => "Freeze another player for a duration of time.";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.Blue;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
{
OptionsScreenshot = ExampleAssets.Banner,
MaxRoleCount = 2,
};
}
@@ -1,30 +0,0 @@
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Example.Roles;
public class NeutralKillerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Neutral Killer";
public string RoleDescription => "Neutral who can kill.";
public string RoleLongDescription => RoleDescription;
public Color RoleColor => Color.magenta;
public ModdedRoleTeams Team => ModdedRoleTeams.Neutral;
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
{
UseVanillaKillButton = true,
CanGetKilled = true,
CanUseVent = true,
};
public override void SpawnTaskHeader(PlayerControl playerControl)
{
// remove existing task header.
}
public override bool DidWin(GameOverReason gameOverReason)
{
return GameManager.Instance.DidHumansWin(gameOverReason);
}
}
-26
View File
@@ -1,26 +0,0 @@
using MiraAPI.Roles;
using UnityEngine;
namespace MiraAPI.Example.Roles;
public class TeleporterRole : CrewmateRole, ICustomRole
{
public string RoleName => "Teleporter";
public string RoleLongDescription => "Zoom out and teleport across the map!";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => new Color32(221, 176, 152, 255);
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public CustomRoleConfiguration Configuration => new CustomRoleConfiguration(this)
{
OptionsScreenshot = ExampleAssets.Banner,
CanModifyChance = false,
DefaultChance = 73,
DefaultRoleCount = 4,
};
public bool CanLocalPlayerSeeRole(PlayerControl player)
{
return true;
}
}
-44
View File
@@ -1,44 +0,0 @@
using MiraAPI.Utilities;
using Reactor.Localization.Utilities;
using UnityEngine;
namespace MiraAPI.Colors;
/// <summary>
/// Represents a custom color with a main color and a shadow color.
/// </summary>
/// <param name="name">The name of the option.</param>
/// <param name="mainColor">The main color.</param>
/// <param name="shadowColor">The shadow color.</param>
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
{
/// <summary>
/// Gets the main color.
/// </summary>
public Color32 MainColor { get; } = mainColor;
/// <summary>
/// Gets the shadow color.
/// </summary>
public Color32 ShadowColor { get; } = shadowColor;
/// <summary>
/// Gets the name of the color.
/// </summary>
public StringNames Name { get; } = name;
/// <inheritdoc />
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
{
}
/// <inheritdoc />
public CustomColor(string name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
{
}
/// <inheritdoc />
public CustomColor(string name, Color32 mainColor, Color32 shadowColor) : this(CustomStringName.CreateAndRegister(name), mainColor, shadowColor)
{
}
}
-32
View File
@@ -1,32 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
namespace MiraAPI.Colors;
/// <summary>
/// Used to register and track custom colors.
/// </summary>
public static class PaletteManager
{
/// <summary>
/// Gets all registered custom colors.
/// </summary>
public static CustomColor[] RegisteredColors => [.. CustomColors];
internal static readonly List<CustomColor> CustomColors = [];
internal static void RegisterAllColors()
{
var colors = CustomColors.Select(x => x.MainColor).ToArray();
var shadowColors = CustomColors.Select(x => x.ShadowColor).ToArray();
var stringNames = CustomColors.Select(x => x.Name).ToArray();
Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors);
Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors);
Palette.ColorNames = Palette.ColorNames.ToArray().AddRangeToArray(stringNames);
Palette.TextColors = Palette.TextColors.ToArray().AddRangeToArray(colors);
Palette.TextOutlineColors = Palette.TextOutlineColors.ToArray().AddRangeToArray(shadowColors);
}
}
@@ -1,9 +0,0 @@
using System;
namespace MiraAPI.Colors;
/// <summary>
/// Used to mark a class for custom colors registration.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class RegisterCustomColorsAttribute : Attribute;
@@ -1,44 +0,0 @@
using MiraAPI.Hud;
namespace MiraAPI.Events.Mira;
/// <summary>
/// Invoked when a Mira Button click is cancelled. Do not use for vanilla buttons.
/// </summary>
/// <typeparam name="T">The Mira Button type.</typeparam>
public sealed class MiraButtonCancelledEvent<T> : MiraEvent where T : CustomActionButton
{
/// <summary>
/// Gets Mira Button whose click was cancelled.
/// </summary>
public T Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonCancelledEvent{T}"/> class.
/// </summary>
/// <param name="button">The Mira Button whose click was cancelled.</param>
public MiraButtonCancelledEvent(T button)
{
Button = button;
}
}
/// <summary>
/// Invoked when a Mira Button click is cancelled. Do not use for vanilla buttons.
/// </summary>
public sealed class MiraButtonCancelledEvent : MiraEvent
{
/// <summary>
/// Gets Mira Button whose click was cancelled.
/// </summary>
public CustomActionButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonCancelledEvent"/> class.
/// </summary>
/// <param name="button">The Mira Button whose click was cancelled.</param>
public MiraButtonCancelledEvent(CustomActionButton button)
{
Button = button;
}
}
@@ -1,51 +0,0 @@
using MiraAPI.Hud;
namespace MiraAPI.Events.Mira;
/// <summary>
/// Button click event for Mira Buttons only. Do not use for vanilla buttons.
/// </summary>
/// <typeparam name="T">The Mira Button type.</typeparam>
public sealed class MiraButtonClickEvent<T> : MiraCancelableEvent where T : CustomActionButton
{
/// <summary>
/// Gets Mira Button that was clicked.
/// </summary>
public T Button { get; }
/// <summary>
/// Gets the generic click event that is invoked before this button-specific event.
/// </summary>
public MiraButtonClickEvent GenericClickEvent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonClickEvent{T}"/> class.
/// </summary>
/// <param name="button">The Mira Button that was clicked.</param>
/// <param name="genericClickEvent">The generic click event invoked before button-specific events.</param>
public MiraButtonClickEvent(T button, MiraButtonClickEvent genericClickEvent)
{
GenericClickEvent = genericClickEvent;
Button = button;
}
}
/// <summary>
/// Button click event for Mira Buttons only. Do not use for vanilla buttons.
/// </summary>
public sealed class MiraButtonClickEvent : MiraCancelableEvent
{
/// <summary>
/// Gets Mira Button that was clicked.
/// </summary>
public CustomActionButton Button { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraButtonClickEvent"/> class.
/// </summary>
/// <param name="button">The Mira Button that was clicked.</param>
public MiraButtonClickEvent(CustomActionButton button)
{
Button = button;
}
}
-28
View File
@@ -1,28 +0,0 @@
namespace MiraAPI.Events;
/// <summary>
/// Abstract class for Mira Events that can be cancelled.
/// </summary>
public abstract class MiraCancelableEvent : MiraEvent
{
/// <summary>
/// Gets a value indicating whether the event is cancelled.
/// </summary>
public bool IsCancelled { get; private set; }
/// <summary>
/// Cancels the event.
/// </summary>
public void Cancel()
{
IsCancelled = true;
}
/// <summary>
/// Uncancels the event.
/// </summary>
public void UnCancel()
{
IsCancelled = false;
}
}
-6
View File
@@ -1,6 +0,0 @@
namespace MiraAPI.Events;
/// <summary>
/// Abstract class for Mira Events.
/// </summary>
public abstract class MiraEvent;
-81
View File
@@ -1,81 +0,0 @@
using System;
using System.Collections.Generic;
namespace MiraAPI.Events;
/// <summary>
/// Mira Event manager.
/// </summary>
public static class MiraEventManager
{
private static readonly Dictionary<Type, List<MiraEventWrapper>> EventWrappers = [];
/// <summary>
/// Invoke an event.
/// </summary>
/// <param name="eventInstance">The event instance.</param>
/// <typeparam name="T">Type of Event.</typeparam>
/// <returns>If there was an event handler invoked for this event, return true. Otherwise, return false.</returns>
public static bool InvokeEvent<T>(T eventInstance) where T : MiraEvent
{
EventWrappers.TryGetValue(typeof(T), out var handlers);
if (handlers == null)
{
return false;
}
foreach (var handler in handlers)
{
((Action<T>)handler.EventHandler).Invoke(eventInstance);
}
return true;
}
/// <summary>
/// Invoke an event and use a specific type to find the handlers.
/// </summary>
/// <param name="eventInstance">The event instance.</param>
/// <param name="type">The type to use for handler lookup.</param>
/// <returns>If there was an event handler invoked for this event, return true. Otherwise, return false.</returns>
public static bool InvokeEvent(MiraEvent eventInstance, Type type)
{
EventWrappers.TryGetValue(type, out var handlers);
if (handlers == null)
{
return false;
}
foreach (var handler in handlers)
{
handler.EventHandler.DynamicInvoke(eventInstance);
}
return true;
}
/// <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>
/// <typeparam name="T">Type of event.</typeparam>
public static void RegisterEventHandler<T>(Action<T> handler, int priority = 0) where T : MiraEvent
{
if (!EventWrappers.ContainsKey(typeof(T)))
{
EventWrappers.Add(typeof(T), []);
}
var handlers = EventWrappers[typeof(T)];
handlers.Add(new MiraEventWrapper(handler, priority));
}
internal static void SortAllHandlers()
{
foreach (var handlers in EventWrappers.Values)
{
handlers.Sort((a, b) => a.Priority.CompareTo(b.Priority));
}
}
}
-30
View File
@@ -1,30 +0,0 @@
using System;
namespace MiraAPI.Events;
/// <summary>
/// Wrapper for event handlers.
/// </summary>
public class MiraEventWrapper
{
/// <summary>
/// Gets the event handler delegate.
/// </summary>
public Delegate EventHandler { get; }
/// <summary>
/// Gets the priority of the handler. Higher values are called first.
/// </summary>
public int Priority { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MiraEventWrapper"/> class.
/// </summary>
/// <param name="eventHandler">The action for the event handler.</param>
/// <param name="priority">The priority of the handler.</param>
public MiraEventWrapper(Delegate eventHandler, int priority)
{
EventHandler = eventHandler;
Priority = priority;
}
}
@@ -1,28 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked after a player is murdered. Only called after a successful murder. This event is not cancelable.
/// </summary>
public class AfterMurderEvent : MiraEvent
{
/// <summary>
/// Gets the player that killed the target.
/// </summary>
public PlayerControl Source { get; }
/// <summary>
/// Gets the player that was killed.
/// </summary>
public PlayerControl Target { 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)
{
Source = source;
Target = target;
}
}
@@ -1,28 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked before a player is murdered. This event is cancelable.
/// </summary>
public sealed class BeforeMurderEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the player that is killing the target.
/// </summary>
public PlayerControl Source { get; }
/// <summary>
/// Gets the player that is being killed.
/// </summary>
public PlayerControl Target { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BeforeMurderEvent"/> class.
/// </summary>
/// <param name="source">The player that is killing the target.</param>
/// <param name="target">The player that is being killed.</param>
public BeforeMurderEvent(PlayerControl source, PlayerControl target)
{
Source = source;
Target = target;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the end game screen is shown. Non cancelable.
/// </summary>
public class GameEndEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the EndGameManager.
/// </summary>
public EndGameManager EndGameManager { get; }
/// <summary>
/// Initializes a new instance of the <see cref="GameEndEvent"/> class.
/// </summary>
/// <param name="manager">The instance of the EndGameManager.</param>
public GameEndEvent(EndGameManager manager)
{
EndGameManager = manager;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// The event that is invoked when the intro cutscene is shown. Non cancelable.
/// </summary>
public class IntroBeginEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the IntroCutscene.
/// </summary>
public IntroCutscene IntroCutscene { get; }
/// <summary>
/// Initializes a new instance of the <see cref="IntroBeginEvent"/> class.
/// </summary>
/// <param name="introCutscene">The intro cutscene.</param>
public IntroBeginEvent(IntroCutscene introCutscene)
{
IntroCutscene = introCutscene;
}
}
@@ -1,30 +0,0 @@
using AmongUs.GameOptions;
namespace MiraAPI.Events.Vanilla.Gameplay;
/// <summary>
/// Event that is invoked after a player's role is set. This event is not cancelable.
/// </summary>
public class SetRoleEvent : MiraEvent
{
/// <summary>
/// Gets the player whos role was changed.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the role that the player was set to.
/// </summary>
public RoleTypes Role { get; }
/// <summary>
/// Initializes a new instance of the <see cref="SetRoleEvent"/> class.
/// </summary>
/// <param name="player">The player.</param>
/// <param name="role">The new role.</param>
public SetRoleEvent(PlayerControl player, RoleTypes role)
{
Player = player;
Role = role;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Map;
/// <summary>
/// Event fired when a player closes the doors in a room.
/// </summary>
public class CloseDoorsEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the room that the doors were closed in.
/// </summary>
public SystemTypes Room { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CloseDoorsEvent"/> class.
/// </summary>
/// <param name="room">The room that was closed.</param>
public CloseDoorsEvent(SystemTypes room)
{
Room = room;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Map;
/// <summary>
/// Sabotage from Vanilla Among Us.
/// </summary>
public class PlayerOpenSabotageEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the MapBehaviour.
/// </summary>
public MapBehaviour MapBehaviour { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerOpenSabotageEvent"/> class.
/// </summary>
/// <param name="mapBehaviour">The MapBehaviour.</param>
public PlayerOpenSabotageEvent(MapBehaviour mapBehaviour)
{
MapBehaviour = mapBehaviour;
}
}
@@ -1,34 +0,0 @@
namespace MiraAPI.Events.Vanilla.Map;
/// <summary>
/// Cancelable event that is invoked before a system is updated. Usually used for sabotage events.
/// </summary>
public class UpdateSystemEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the type of system that will be updated.
/// </summary>
public SystemTypes SystemType { get; }
/// <summary>
/// Gets the player that is updating the system.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the byte amount the system is being updated by.
/// </summary>
public byte Amount { get; }
/// <summary>
/// Initializes a new instance of the <see cref="UpdateSystemEvent"/> class.
/// </summary>
/// <param name="systemType">The SystemType being updated.</param>
/// <param name="amount">Amount to update System to.</param>
public UpdateSystemEvent(SystemTypes systemType, PlayerControl player, byte amount)
{
SystemType = systemType;
Player = player;
Amount = amount;
}
}
@@ -1,21 +0,0 @@
namespace MiraAPI.Events.Vanilla.Meeting;
/// <summary>
/// The event that is invoked when a player is ejected. Non-cancelable.
/// </summary>
public class EjectionEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the ExileController.
/// </summary>
public ExileController ExileController { get; }
/// <summary>
/// Initializes a new instance of the <see cref="EjectionEvent"/> class.
/// </summary>
/// <param name="controller">The exile controller.</param>
public EjectionEvent(ExileController controller)
{
ExileController = controller;
}
}
@@ -1,35 +0,0 @@
namespace MiraAPI.Events.Vanilla.Meeting;
/// <summary>
/// The event that is invoked when a meeting is called.
/// </summary>
public class StartMeetingEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the player that is calling the meeting.
/// </summary>
public PlayerControl Reporter { get; }
/// <summary>
/// Gets the player that is being reported. Will be null for emergency meeting.
/// </summary>
public NetworkedPlayerInfo? Target { get; }
/// <summary>
/// Gets the body that is being reported. Will be null for emergency meeting.
/// </summary>
public DeadBody? Body { get; }
/// <summary>
/// Initializes a new instance of the <see cref="StartMeetingEvent"/> class.
/// </summary>
/// <param name="reporter">The player who reported the body.</param>
/// <param name="target">The player being reported.</param>
/// <param name="body">The body being reported.</param>
public StartMeetingEvent(PlayerControl reporter, NetworkedPlayerInfo? target, DeadBody? body)
{
Reporter = reporter;
Target = target;
Body = body;
}
}
@@ -1,28 +0,0 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// The event that is invoked when a player completes a task. Non cancelable.
/// </summary>
public class CompleteTaskEvent : MiraEvent
{
/// <summary>
/// Gets the instance of the <see cref="PlayerControl"/>.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the instance of the <see cref="PlayerTask"/> that the player completed.
/// </summary>
public PlayerTask Task { get; }
/// <summary>
/// Initializes a new instance of the <see cref="CompleteTaskEvent"/> class.
/// </summary>
/// <param name="player">The player who completed the task.</param>
/// <param name="task">The task that the player completed.</param>
public CompleteTaskEvent(PlayerControl player, PlayerTask task)
{
Player = player;
Task = task;
}
}
@@ -1,28 +0,0 @@
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// Event that is invoked when a player dies. Non cancelable.
/// </summary>
public class PlayerDeathEvent : MiraEvent
{
/// <summary>
/// Gets the player who died.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the reason the player died.
/// </summary>
public DeathReason DeathReason { 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)
{
Player = player;
DeathReason = reason;
}
}
@@ -1,23 +0,0 @@
using InnerNet;
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// Event that is invoked when a player joins the lobby. Non cancelable.
/// </summary>
public class PlayerJoinEvent : MiraEvent
{
/// <summary>
/// Gets the player who joined.
/// </summary>
public ClientData ClientData { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerJoinEvent"/> class.
/// </summary>
/// <param name="data">The data of the player who joined.</param>
public PlayerJoinEvent(ClientData data)
{
ClientData = data;
}
}
@@ -1,30 +0,0 @@
using InnerNet;
namespace MiraAPI.Events.Vanilla.Player;
/// <summary>
/// Event that is invoked when a player leaves the game. Non cancelable.
/// </summary>
public class PlayerLeaveEvent : MiraEvent
{
/// <summary>
/// Gets the player who left.
/// </summary>
public ClientData ClientData { get; }
/// <summary>
/// Gets the reason why the player left.
/// </summary>
public DisconnectReasons Reason { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerLeaveEvent"/> class.
/// </summary>
/// <param name="data">The data of the player who left.</param>
/// <param name="reason">The reason why the player left.</param>
public PlayerLeaveEvent(ClientData data, DisconnectReasons reason)
{
ClientData = data;
Reason = reason;
}
}
@@ -1,28 +0,0 @@
namespace MiraAPI.Events.Vanilla.Usables;
/// <summary>
/// Event that is invoked when a player enters a vent. This event is cancelable.
/// </summary>
public class EnterVentEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the player that is entering the vent.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the vent that the player is entering.
/// </summary>
public Vent? Vent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="EnterVentEvent"/> class.
/// </summary>
/// <param name="player">The player that is entering the vent.</param>
/// <param name="vent">The vent being entered.</param>
public EnterVentEvent(PlayerControl player, Vent? vent)
{
Player = player;
Vent = vent;
}
}
@@ -1,28 +0,0 @@
namespace MiraAPI.Events.Vanilla.Usables;
/// <summary>
/// Event that is invoked when a player exits a vent. This event is cancelable.
/// </summary>
public class ExitVentEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the player that is exiting the vent.
/// </summary>
public PlayerControl Player { get; }
/// <summary>
/// Gets the vent that the player is exiting.
/// </summary>
public Vent? Vent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ExitVentEvent"/> class.
/// </summary>
/// <param name="player">The player who is exiting the vent.</param>
/// <param name="vent">The vent being exited from.</param>
public ExitVentEvent(PlayerControl player, Vent? vent)
{
Player = player;
Vent = vent;
}
}
@@ -1,15 +0,0 @@
namespace MiraAPI.Events.Vanilla.Usables;
/// <summary>
/// Event for if a <see cref="PlayerControl"/> can use an <see cref="IUsable"/> from Vanilla Among Us. Will always be ran locally.
/// </summary>
public class PlayerCanUseEvent : PlayerUseEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="PlayerCanUseEvent"/> class.
/// </summary>
/// <param name="usable">The IUsable.</param>
public PlayerCanUseEvent(IUsable usable) : base(usable)
{
}
}
@@ -1,34 +0,0 @@
namespace MiraAPI.Events.Vanilla;
/// <summary>
/// Event for <see cref="PlayerControl"/> using a <see cref="IUsable"/> from Vanilla Among Us. Will always be ran locally.
/// </summary>
public class PlayerUseEvent : MiraCancelableEvent
{
/// <summary>
/// Gets the instance of <see cref="IUsable"/> that was used.
/// </summary>
public IUsable Usable { get; }
/// <summary>
/// Gets a value indicating whether the IUsable is a <see cref="Console"/>, <see cref="MapConsole"/>, or <see cref="SystemConsole"/>.
/// </summary>
public bool IsPrimaryConsole { get; }
/// <summary>
/// Gets a value indicating whether the IUsable is a <see cref="Vent"/>.
/// </summary>
public bool IsVent { get; }
/// <summary>
/// Initializes a new instance of the <see cref="PlayerUseEvent"/> class.
/// </summary>
/// <param name="usable">The IUsable.</param>
public PlayerUseEvent(IUsable usable)
{
Usable = usable;
IsPrimaryConsole = usable.TryCast<Console>() || usable.TryCast<SystemConsole>() || usable.TryCast<MapConsole>();
IsVent = usable.TryCast<Vent>();
}
}
+50 -66
View File
@@ -3,82 +3,74 @@
namespace MiraAPI.GameModes;
/// <summary>
/// Base class for custom gamemodes.
/// Base class for custom gamemodes
/// </summary>
public abstract class CustomGameMode
{
/// <summary>
/// Gets the game mode name.
/// Game mode name
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets the game mode description.
/// Game mode description
/// </summary>
public abstract string Description { get; }
/// <summary>
/// Gets the game mode ID.
/// Game mode ID
/// </summary>
public abstract int Id { get; }
/// <summary>
/// Called when Intro Cutscene is destroyed.
/// Called when Intro Cutscene is destroyed
/// </summary>
public virtual void Initialize()
{
}
public virtual void Initialize() { }
/// <summary>
/// Called on HudManager.Start().
/// Called on HudManager.Start()
/// </summary>
/// <param name="instance">Instance of HudManager.</param>
public virtual void HudStart(HudManager instance)
{
}
/// <param name="instance">Instance of HudManager</param>
public virtual void HudStart(HudManager instance) { }
/// <summary>
/// Called every frame on HudManager.Update().
/// Called every frame on HudManager.Update()
/// </summary>
/// <param name="instance">Instance of HudManager.</param>
public virtual void HudUpdate(HudManager instance)
{
}
/// <param name="instance">Instance of HudManager</param>
public virtual void HudUpdate(HudManager instance) { }
/// <summary>
/// Called when a player is killed.
/// Called when a player is killed
/// </summary>
/// <param name="player">PlayerControl that was killed.</param>
public virtual void OnDeath(PlayerControl player)
{
}
/// <param name="player">PlayerControl that was killed</param>
public virtual void OnDeath(PlayerControl player) { }
/// <summary>
/// Calculate Game End logic.
/// Calculate Game End logic
/// </summary>
/// <param name="runOriginal">Should original game end logic be used.</param>
/// <param name="instance">Instance of LogicGameFlowNormal.</param>
/// <param name="runOriginal">Should original game end logic be used</param>
/// <param name="instance">Instance of LogicGameFlowNormal</param>
public virtual void CheckGameEnd(out bool runOriginal, LogicGameFlowNormal instance)
{
runOriginal = true;
}
/// <summary>
/// Custom role assignment.
/// Custom role assignment
/// </summary>
/// <param name="runOriginal">Should original role assignment be used.</param>
/// <param name="instance">Instance of LogicRoleSelection.</param>
/// <param name="runOriginal">Should original role assignment be used</param>
/// <param name="instance">Instance of LogicRoleSelection</param>
public virtual void AssignRoles(out bool runOriginal, LogicRoleSelectionNormal instance)
{
runOriginal = true;
}
/// <summary>
/// Custom murder logic.
/// Custom murder logic
/// </summary>
/// <param name="runOriginal">Should the original murder logic be used.</param>
/// <param name="result">Result of custom murder logic.</param>
/// <param name="target">Target player for murder.</param>
/// <param name="runOriginal">Should original murder logic be used</param>
/// <param name="result">Result of custom murder logic</param>
/// <param name="target">Target player for murder</param>
public virtual void CanKill(out bool runOriginal, out bool result, PlayerControl target)
{
result = false;
@@ -86,69 +78,61 @@ public abstract class CustomGameMode
}
/// <summary>
/// Should Roles Settings be available when this gamemode is selected.
/// Should Roles Settings be available when this gamemode is selected?
/// </summary>
/// <returns>True if Role Settings are enabled in this game mode.</returns>
public virtual bool AreRoleSettingsEnabled() => true;
public virtual bool CanAccessRolesTab() => true;
/// <summary>
/// Should Game Settings be available when this gamemode is selected.
/// Should Game Settings be available when this gamemode is selected?
/// </summary>
/// <returns>True if Game Settings are enabled in this mode.</returns>
public virtual bool AreGameSettingsEnabled() => true;
public virtual bool CanAccessSettingsTab() => true;
/// <summary>
/// Custom winner selection.
/// Custom winner selection
/// </summary>
/// <returns>List of winners or null.</returns>
public virtual List<NetworkedPlayerInfo>? CalculateWinners() => null;
/// <returns>List of winners</returns>
public virtual List<NetworkedPlayerInfo> CalculateWinners() => null;
/// <summary>
/// Show gamemode in Intro Cutscene.
/// Show gamemode in Intro Cutscene
/// </summary>
/// <returns>True if the game mode should be shown in the intro cutscene.</returns>
public virtual bool ShowGameModeIntroCutscene() => false;
public virtual bool ShowCustomRoleScreen() => false;
/// <summary>
/// Can Admin be used in this gamemode.
/// Can Admin be used in this gamemode
/// </summary>
/// <param name="console">Admin Console.</param>
/// <returns>True if Admin console is enabled.</returns>
/// <param name="console">Admin Console</param>
public virtual bool CanUseMapConsole(MapConsole console) => true;
/// <summary>
/// Can a body be reported in this gamemode.
/// Can a body be reported in this gamemode
/// </summary>
/// <param name="body">Target body for reporting.</param>
/// <returns>True if dead bodies can be reported.</returns>
/// <param name="body">Target body for reporting</param>
public virtual bool CanReport(DeadBody body) => true;
/// <summary>
/// Can system consoles be used in this gamemode.
/// Can system consoles be used in this gamemode
/// </summary>
/// <param name="console">System Console.</param>
/// <returns>True if system consoles are enabled in this mode.</returns>
/// <param name="console">System Console</param>
public virtual bool CanUseSystemConsole(SystemConsole console) => true;
/// <summary>
/// Can tasks be interacted with in this gamemode.
/// Can tasks be interacted with in this gamemode
/// </summary>
/// <param name="console">Task console.</param>
/// <returns>True if tasks are enabled in this mode.</returns>
public virtual bool CanUseTasks(Console console) => true;
/// <param name="console">Task console</param>
public virtual bool CanUseConsole(Console console) => true;
/// <summary>
/// Should the sabotage map be used when attempting to open sabotage overlay.
/// Should the sabotage map be used when attempting to open sabotage overlay
/// </summary>
/// <param name="map">MapBehaviour object.</param>
/// <returns>True if the sabotage map should be shown.</returns>
/// <param name="map">MapBehaviour object</param>
public virtual bool ShouldShowSabotageMap(MapBehaviour map) => true;
/// <summary>
/// Can a player vent in this gamemode.
/// Can a player vent in this gamemode
/// </summary>
/// <param name="vent">Target vent.</param>
/// <param name="playerInfo">Player attempting to vent.</param>
/// <returns>True if venting is enabled in this mode.</returns>
/// <param name="vent">Target vent</param>
/// <param name="playerInfo">Player attempting to vent</param>
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo) => true;
}
}
+13 -12
View File
@@ -1,4 +1,5 @@
using Reactor.Utilities;
#nullable enable
using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
@@ -6,14 +7,14 @@ using System.Linq;
namespace MiraAPI.GameModes;
/// <summary>
/// Manages custom gamemodes.
/// Manages custom gamemodes
/// </summary>
public static class CustomGameModeManager
{
/// <summary>
/// List of registered gamemodes.
/// List of registered gamemodes
/// </summary>
internal static readonly Dictionary<int, CustomGameMode> GameModes = [];
public static readonly Dictionary<int, CustomGameMode> GameModes = [];
public static bool IsDefault()
{
@@ -21,14 +22,14 @@ public static class CustomGameModeManager
}
/// <summary>
/// Current gamemode.
/// Current gamemode
/// </summary>
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
public static CustomGameMode? ActiveMode = new DefaultMode();
/// <summary>
/// Set current gamemode.
/// Set current gamemode
/// </summary>
/// <param name="id">gamemode ID.</param>
/// <param name="id">gamemode ID</param>
public static void SetGameMode(int id)
{
if (GameModes.TryGetValue(id, out var gameMode))
@@ -41,9 +42,9 @@ public static class CustomGameModeManager
}
/// <summary>
/// Register gamemode from type.
/// Register gamemode from type
/// </summary>
/// <param name="gameModeType">Type of gamemode class, should inherit from <see cref="CustomGameMode"/>.</param>
/// <param name="gameModeType">Type of gamemode class, should inherit from <see cref="CustomGameMode"/></param>
internal static void RegisterGameMode(Type gameModeType)
{
if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType))
@@ -59,7 +60,7 @@ public static class CustomGameModeManager
Logger<MiraApiPlugin>.Error($"Failed to create instance of {gameModeType.Name}");
return;
}
if (GameModes.Any(x => x.Key == gameMode.Id))
{
Logger<MiraApiPlugin>.Error($"ID for gamemode {gameMode.Name} already exists!");
@@ -68,4 +69,4 @@ public static class CustomGameModeManager
GameModes.Add(gameMode.Id, gameMode);
}
}
}
+2 -1
View File
@@ -1,8 +1,9 @@
namespace MiraAPI.GameModes;
[RegisterGameMode]
public class DefaultMode : CustomGameMode
{
public override string Name => "Default";
public override string Description => "Default Among Us GameMode";
public override int Id => 0;
}
}
@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Reflection;
namespace MiraAPI.GameModes;
[AttributeUsage(AttributeTargets.Class)]
public class RegisterGameModeAttribute : Attribute
{
private static readonly HashSet<Assembly> RegisteredAssemblies = [];
internal static void Register(Assembly assembly)
{
if (!RegisteredAssemblies.Add(assembly))
{
return;
}
foreach (var type in assembly.GetTypes())
{
var attribute = type.GetCustomAttribute<RegisterGameModeAttribute>();
if (attribute != null)
{
CustomGameModeManager.RegisterGameMode(type);
}
}
}
}
+11 -24
View File
@@ -4,40 +4,27 @@ using UnityEngine;
namespace MiraAPI.GameOptions;
/// <summary>
/// Base class for option groups. An option group is a collection of options that are displayed together in the options menu.
/// </summary>
public abstract class AbstractOptionGroup
{
internal List<IModdedOption> Options { get; } = [];
public List<IModdedOption> Options { get; } = [];
/// <summary>
/// Gets the name of the group. Visible in options menu.
/// The name of the group. Visible in options menu.
/// </summary>
public abstract string GroupName { get; }
/// <summary>
/// Gets the function that determines whether the group should be visible or not.
/// A function that returns whether the group should be visible or not.
/// </summary>
public virtual Func<bool> GroupVisible => () => true;
/// <summary>
/// Gets the group color. This is used to color the group in the options menu.
/// The group color. This is used to color the group in the options menu.
/// </summary>
public virtual Color GroupColor => Color.clear;
/// <summary>
/// Gets the group priority. This is used to determine the order in which groups are displayed in the options menu.
/// Zero is the highest priority, and the default value is the max uint value.
/// The role the group is associated with. This is used for the advanced role options menu.
/// </summary>
public virtual uint GroupPriority => uint.MaxValue;
/// <summary>
/// Gets the role the group is associated with. This is used for the advanced role options menu.
/// </summary>
public virtual Type? AdvancedRole => null;
internal bool AllOptionsHidden { get; set; }
internal CategoryHeaderMasked? Header { get; set; }
}
public virtual Type AdvancedRole => null;
}
@@ -4,31 +4,24 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Attribute for creating an enum option.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null, Type? roleType = null)
public class ModdedEnumOptionAttribute(string title, Type enumType, Type roleType = null)
: ModdedOptionAttribute(title, roleType)
{
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
internal override IModdedOption CreateOption(object value, PropertyInfo property)
{
var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values, RoleType);
var opt = new ModdedEnumOption(Title, (int)value, enumType, RoleType);
return opt;
}
/// <inheritdoc />
public override void SetValue(object value)
{
var opt = HolderOption as ModdedEnumOption;
opt?.SetValue((int)value);
ModdedEnumOption opt = HolderOption as ModdedEnumOption;
opt.SetValue((int)value);
}
/// <inheritdoc />
public override object GetValue()
{
return HolderOption is ModdedEnumOption opt
? Enum.ToObject(enumType, opt.Value)
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
ModdedEnumOption opt = HolderOption as ModdedEnumOption;
return Enum.ToObject(enumType, opt.Value);
}
}
}
@@ -1,43 +1,34 @@
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Utilities;
using System;
using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// A number option attribute for the modded options system.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class ModdedNumberOptionAttribute(
string title,
float min,
float max,
float increment = 1,
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
NumberSuffixes suffixType = NumberSuffixes.None,
bool zeroInfinity = false,
Type? roleType = null)
Type roleType = null)
: ModdedOptionAttribute(title, roleType)
{
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
internal override IModdedOption CreateOption(object value, PropertyInfo property)
{
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, zeroInfinity, RoleType);
var toggleOpt = new ModdedNumberOption(Title, (float)value, min, max, increment, suffixType, zeroInfinity, RoleType);
return toggleOpt;
}
/// <inheritdoc />
public override void SetValue(object value)
{
var opt = HolderOption as ModdedNumberOption;
opt?.SetValue((float)value);
ModdedNumberOption toggleOpt = HolderOption as ModdedNumberOption;
toggleOpt.SetValue((float)value);
}
/// <inheritdoc />
public override object GetValue()
{
if (HolderOption is ModdedNumberOption opt)
{
return opt.Value;
}
throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption");
ModdedNumberOption toggleOpt = HolderOption as ModdedNumberOption;
return toggleOpt.Value;
}
}
}
@@ -3,37 +3,18 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Represents an attribute that is used to define a modded option.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="roleType">Optional parameter to specify a role Type.</param>
[AttributeUsage(AttributeTargets.Property)]
public abstract class ModdedOptionAttribute(string title, Type? roleType = null) : Attribute
public abstract class ModdedOptionAttribute(string title, Type roleType = null) : Attribute
{
internal IModdedOption? HolderOption { get; set; }
/// <summary>
/// Gets the title of the option.
/// </summary>
public IModdedOption HolderOption { get; set; }
public string Title { get; private set; } = title;
protected Type RoleType { get; private set; } = roleType;
/// <summary>
/// Gets the role type of the option.
/// </summary>
protected Type? RoleType { get; private set; } = roleType;
/// <summary>
/// Sets the value of the option.
/// </summary>
/// <param name="value">The new value as an object.</param>
public abstract void SetValue(object value);
/// <summary>
/// Gets the value of the option.
/// </summary>
/// <returns>The value of the option as an object.</returns>
public abstract void SetValue(object val);
public abstract object GetValue();
internal abstract IModdedOption? CreateOption(object? value, PropertyInfo property);
}
internal abstract IModdedOption CreateOption(object value, PropertyInfo property);
}
@@ -0,0 +1,27 @@
using MiraAPI.GameOptions.OptionTypes;
using System;
using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
public class ModdedStringOptionAttribute(string title, string[] values, Type roleType = null)
: ModdedOptionAttribute(title, roleType)
{
internal override IModdedOption CreateOption(object value, PropertyInfo property)
{
var opt = new ModdedStringOption(Title, (int)value, values, RoleType);
return opt;
}
public override void SetValue(object value)
{
ModdedStringOption opt = HolderOption as ModdedStringOption;
opt.SetValue((int)value);
}
public override object GetValue()
{
ModdedStringOption opt = HolderOption as ModdedStringOption;
return opt.Value;
}
}
@@ -4,34 +4,23 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
/// <summary>
/// Attribute for a toggle option.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="roleType">An optional role type.</param>
[AttributeUsage(AttributeTargets.Property)]
public class ModdedToggleOptionAttribute(string title, Type? roleType = null) : ModdedOptionAttribute(title, roleType)
public class ModdedToggleOptionAttribute(string title, Type roleType = null) : ModdedOptionAttribute(title, roleType)
{
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
internal override IModdedOption CreateOption(object value, PropertyInfo property)
{
var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false), RoleType);
var toggleOpt = new ModdedToggleOption(Title, (bool)value, RoleType);
return toggleOpt;
}
/// <inheritdoc />
public override void SetValue(object value)
{
var opt = HolderOption as ModdedToggleOption;
opt?.SetValue((bool)value);
ModdedToggleOption toggleOpt = HolderOption as ModdedToggleOption;
toggleOpt.SetValue((bool)value);
}
/// <inheritdoc />
public override object GetValue()
{
if (HolderOption is ModdedToggleOption opt)
{
return opt.Value;
}
throw new InvalidOperationException($"Holder option for {Title} is not a ModdedToggleOption.");
ModdedToggleOption toggleOpt = HolderOption as ModdedToggleOption;
return toggleOpt.Value;
}
}
}
+16 -16
View File
@@ -1,24 +1,24 @@
using System;
using MiraAPI.PluginLoading;
using System;
using BepInEx.Configuration;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using UnityEngine;
namespace MiraAPI.GameOptions;
public interface IModdedOption
{
uint Id { get; }
string Title { get; }
StringNames StringName { get; }
IMiraPlugin? ParentMod { get; set; }
BaseGameSetting Data { get; }
Type? AdvancedRole { get; set; }
OptionBehaviour? OptionBehaviour { get; }
Func<bool> Visible { get; set; }
ConfigDefinition? ConfigDefinition { get; set; }
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
float GetFloatData();
NetData GetNetData();
void HandleNetData(byte[] data);
}
public uint Id { get; }
public BaseGameSetting Data { get; }
public IMiraPlugin ParentMod { get; set; }
public Type AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; protected set; }
public string Title { get; }
public StringNames StringName { get; }
public Func<bool> Visible { get; set; }
public ConfigDefinition ConfigDefinition { get; }
public void ValueChanged(OptionBehaviour optionBehaviour);
public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
public NetData GetNetData();
public void HandleNetData(byte[] data);
}
+64 -90
View File
@@ -1,36 +1,32 @@
using System;
using HarmonyLib;
using MiraAPI.GameOptions.Attributes;
using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BepInEx.Configuration;
using HarmonyLib;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
namespace MiraAPI.GameOptions;
/// <summary>
/// Handles modded options.
/// </summary>
public static class ModdedOptionsManager
public class ModdedOptionsManager
{
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = [];
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = new();
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
internal static readonly List<AbstractOptionGroup> Groups = [];
internal static uint NextId => _nextId++;
public static readonly Dictionary<uint, IModdedOption> ModdedOptions = new();
public static readonly List<AbstractOptionGroup> Groups = [];
public static uint NextId => _nextId++;
private static uint _nextId = 1;
internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo)
{
if (Activator.CreateInstance(type) is not AbstractOptionGroup group)
{
Logger<MiraApiPlugin>.Error($"Failed to create group from {type.Name}");
return false;
}
@@ -43,14 +39,10 @@ public static class ModdedOptionsManager
Groups.Add(group);
TypeToGroup.Add(type, group);
pluginInfo.OptionGroups.Add(group);
typeof(OptionGroupSingleton<>).MakeGenericType(type)
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
.SetValue(null, group);
return true;
}
internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo)
{
if (!TypeToGroup.TryGetValue(type, out var group))
@@ -64,15 +56,11 @@ public static class ModdedOptionsManager
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
return;
}
RegisterOption(option, group, property.Name, pluginInfo);
RegisterOption(option, group, pluginInfo);
}
internal static void RegisterAttributeOption(
Type type,
ModdedOptionAttribute attribute,
PropertyInfo property,
MiraPluginInfo pluginInfo)
internal static void RegisterAttributeOption(Type type, ModdedOptionAttribute attribute, PropertyInfo property, MiraPluginInfo pluginInfo)
{
if (OptionAttributes.ContainsKey(property))
{
@@ -87,13 +75,13 @@ public static class ModdedOptionsManager
}
var option = attribute.CreateOption(property.GetValue(group), property);
if (option == null)
{
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
return;
}
var setterOriginal = property.GetSetMethod();
var setterPatch = typeof(ModdedOptionsManager).GetMethod(nameof(PropertySetterPatch));
PluginSingleton<MiraApiPlugin>.Instance.Harmony.Patch(setterOriginal, postfix: new HarmonyMethod(setterPatch));
@@ -104,94 +92,80 @@ public static class ModdedOptionsManager
OptionAttributes.Add(property, attribute);
attribute.HolderOption = option;
RegisterOption(option, group, property.Name, pluginInfo);
RegisterOption(option, group, pluginInfo);
}
private static void RegisterOption(
IModdedOption option,
AbstractOptionGroup group,
string propertyName,
MiraPluginInfo pluginInfo)
private static void RegisterOption(IModdedOption option, AbstractOptionGroup group, MiraPluginInfo pluginInfo)
{
var groupName = group.GetType().FullName;
option.ConfigDefinition = new ConfigDefinition(groupName, propertyName);
option.ParentMod = pluginInfo.MiraPlugin;
option.AdvancedRole = group.AdvancedRole;
pluginInfo.Options.Add(option);
ModdedOptions.Add(option.Id, option);
group.Options.Add(option);
}
internal static void SyncAllOptions(int targetId = -1)
internal static void SyncAllOptions(int targetId=-1)
{
var chunks = ModdedOptions.Values.Select(option => option.GetNetData()).ChunkNetData(1000);
while (chunks.Count > 0)
List<NetData> data = [];
var count = 0;
foreach (var netData in ModdedOptions.Values.Select(option => option.GetNetData()))
{
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, chunks.Dequeue());
data.Add(netData);
count += netData.GetLength();
if (count <= 1000)
{
continue;
}
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
data.Clear();
count = 0;
}
if (data.Count > 0)
{
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
}
}
internal static void HandleSyncOptions(NetData[] data)
{
// necessary to disable then re-enable this setting
// 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())
{
oldConfigSetting.Add(plugin, plugin.PluginConfig.SaveOnConfigSet);
plugin.PluginConfig.SaveOnConfigSet = false;
}
foreach (var netData in data)
{
if (!ModdedOptions.TryGetValue(netData.Id, out var option))
{
continue;
}
Logger<MiraApiPlugin>.Error("Handling option " + option.Title);
option.HandleNetData(netData.Data);
}
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
{
plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
}
if (LobbyInfoPane.Instance)
{
LobbyInfoPane.Instance.RefreshPane();
}
}
/// <summary>
/// Patches the setter of a property to update the value of the option.
/// </summary>
/// <param name="__originalMethod">The original setter method.</param>
/// <param name="value">The new object value.</param>
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
{
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __originalMethod).Value;
attribute.SetValue(value);
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value;
attribute?.SetValue(value);
}
/// <summary>
/// Patches the getter of a property to return the value of the option.
/// </summary>
/// <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>
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
{
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value;
if (attribute == null)
{
return true;
}
__result = attribute.GetValue();
return false;
}
}
}
+18 -14
View File
@@ -1,19 +1,23 @@
using System.Linq;
using System;
using System.Linq;
namespace MiraAPI.GameOptions;
/// <summary>
/// Singleton for option groups.
/// </summary>
/// <typeparam name="T">The option group type.</typeparam>
public static class OptionGroupSingleton<T> where T : AbstractOptionGroup
public class OptionGroupSingleton<T> where T : AbstractOptionGroup
{
private static T? _instance;
private static T _instance;
/// <summary>
/// Gets the instance of the option group.
/// </summary>
#pragma warning disable CA1000
public static T Instance => _instance ??= ModdedOptionsManager.Groups.OfType<T>().Single();
#pragma warning restore CA1000
}
public static T Instance
{
get => _instance ??= ModdedOptionsManager.Groups.OfType<T>().Single();
set
{
if (_instance != null)
{
throw new InvalidOperationException($"Instance for {typeof(T)} is already set");
}
_instance = value;
}
}
}
@@ -1,195 +1,77 @@
using System;
using System.Globalization;
using Reactor.Localization.Utilities;
using System;
using System.Linq;
using System.Text;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using MiraAPI.Networking;
using Reactor.Localization.Utilities;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// An option for selecting an enum value.
/// </summary>
public class 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.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as an int.</param>
/// <param name="enumType">The Enum type.</param>
/// <param name="values">An option list of string values to use in place of the enum name.</param>
/// <param name="roleType">An optional role type to specify for a specific role.</param>
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType)
public ModdedEnumOption(string title, int defaultValue, Type enumType, Type roleType=null) : base(title, defaultValue, roleType)
{
Values = values ?? Enum.GetNames(enumType);
Values = Enum.GetNames(enumType);
Data = ScriptableObject.CreateInstance<StringGameSetting>();
var data = (StringGameSetting)Data;
data.Title = StringName;
data.Type = global::OptionTypes.String;
data.Values = values is null ?
Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray()
: values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Value;
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
// SetUpFromData method doesnt work correctly so we must set the values manually
stringOption.Title = StringName;
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
stringOption.Values = ((StringGameSetting)Data).Values;
stringOption.Value = Value;
OptionBehaviour = stringOption;
return stringOption;
}
/// <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 string GetHudStringText()
{
return Title + ": " + Values[Value];
}
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return optionBehaviour.GetInt();
}
/// <inheritdoc />
protected override void OnValueChanged(int newValue)
public override void OnValueChanged(int newValue)
{
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false);
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
if (!OptionBehaviour)
{
return;
}
if (OptionBehaviour is StringOption opt)
var opt = OptionBehaviour as StringOption;
if (opt)
{
opt.Value = newValue;
}
}
}
/// <summary>
/// An option for selecting an enum value.
/// </summary>
/// <typeparam name="T">The enum type.</typeparam>
public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
{
/// <summary>
/// Gets the string values of the enum.
/// </summary>
public string[]? Values { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as an int.</param>
/// <param name="enumType">The Enum type.</param>
/// <param name="values">An option list of string values to use in place of the enum name.</param>
/// <param name="roleType">An optional role type to specify for a specific role.</param>
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, Type? roleType = null) : base(title, defaultValue, roleType)
{
Values = values ?? Enum.GetNames(typeof(T));
Data = ScriptableObject.CreateInstance<StringGameSetting>();
var data = (StringGameSetting)Data;
data.Title = StringName;
data.Type = global::OptionTypes.String;
data.Values = values is null ?
Enum.GetNames(typeof(T)).Select(CustomStringName.CreateAndRegister).ToArray()
: values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Convert.ToInt32(Value);
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
// SetUpFromData method doesnt work correctly so we must set the values manually
stringOption.Title = StringName;
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
stringOption.Value = Convert.ToInt32(Value);
OptionBehaviour = stringOption;
return stringOption;
}
/// <inheritdoc />
public override float GetFloatData()
{
return Convert.ToSingle(Value);
}
/// <inheritdoc />
public override NetData GetNetData()
{
return new NetData(Id, Encoding.Unicode.GetBytes(Convert.ToString(Value)));
}
/// <inheritdoc />
public override void HandleNetData(byte[] data)
{
SetValue((T)Enum.Parse(typeof(T), Encoding.Unicode.GetString(data)));
}
/// <inheritdoc />
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString());
}
/// <inheritdoc />
protected override void OnValueChanged(T newValue)
{
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(Convert.ToInt32(newValue)), false);
if (!OptionBehaviour)
{
return;
}
if (OptionBehaviour is StringOption opt)
{
opt.Value = Convert.ToInt32(newValue);
}
}
}
}
@@ -1,62 +1,25 @@
using AmongUs.GameOptions;
using System;
using AmongUs.GameOptions;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using System;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// Represents a modded number option.
/// </summary>
public class ModdedNumberOption : ModdedOption<float>
{
/// <summary>
/// Gets the minimum value of the option.
/// </summary>
public float Min { get; }
/// <summary>
/// Gets the maximum value of the option.
/// </summary>
public float Min { get;}
public float Max { get; }
/// <summary>
/// Gets the increment value of the option.
/// </summary>
public float Increment { get; }
/// <summary>
/// Gets the suffix type of the option.
/// </summary>
public MiraNumberSuffixes SuffixType { get; }
/// <summary>
/// Gets a value indicating whether zero is infinity.
/// </summary>
public NumberSuffixes SuffixType { get; }
public bool ZeroInfinity { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
/// </summary>
/// <param name="title">The title of the option.</param>
/// <param name="defaultValue">The default value as a float.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
/// <param name="increment">The increment.</param>
/// <param name="suffixType">The suffix type.</param>
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
/// <param name="roleType">An optional role type.</param>
public ModdedNumberOption(
string title,
float defaultValue,
float min,
float max,
float increment,
MiraNumberSuffixes suffixType,
bool zeroInfinity = false,
Type? roleType = null) : base(title, defaultValue, roleType)
public ModdedNumberOption(string title, float defaultValue, float min, float max, float increment, NumberSuffixes suffixType, bool zeroInfinity=false, Type roleType=null) : base(title, defaultValue, roleType)
{
Min = min;
Max = max;
@@ -65,93 +28,77 @@ public class ModdedNumberOption : ModdedOption<float>
ZeroInfinity = zeroInfinity;
Value = Mathf.Clamp(defaultValue, min, max);
Data = ScriptableObject.CreateInstance<FloatGameSetting>();
var data = (FloatGameSetting)Data;
data.Type = global::OptionTypes.Float;
data.Title = StringName;
data.Value = Value;
data.Increment = Increment;
data.ValidRange = new FloatRange(Min, Max);
data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0"; // not sure if the == checks will work since this is floats but we'll find out
data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0";
data.ZeroIsInfinity = ZeroInfinity;
data.SuffixType = (NumberSuffixes)SuffixType;
data.SuffixType = SuffixType;
data.OptionName = FloatOptionNames.Invalid;
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(
ToggleOption toggleOpt,
NumberOption numberOpt,
StringOption stringOpt,
Transform container)
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
numberOption.SetUpFromData(Data, 20);
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
numberOption.Title = StringName;
numberOption.Value = Value;
numberOption.Increment = Increment;
numberOption.ValidRange = new FloatRange(Min, Max);
numberOption.FormatString = (Data as FloatGameSetting)?.FormatString ?? "0.0";
numberOption.FormatString = ((FloatGameSetting)Data).FormatString;
numberOption.ZeroIsInfinity = ZeroInfinity;
numberOption.SuffixType = (NumberSuffixes)SuffixType;
numberOption.SuffixType = SuffixType;
numberOption.floatOptionName = FloatOptionNames.Invalid;
OptionBehaviour = numberOption;
return numberOption;
}
/// <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.ToSingle(data));
}
/// <inheritdoc />
public override string GetHudStringText()
{
return Title + ": " + Value + Helpers.GetSuffix(SuffixType);
}
public override float GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return Mathf.Clamp(optionBehaviour.GetFloat(), Min, Max);
}
/// <inheritdoc />
protected override void OnValueChanged(float newValue)
public override void OnValueChanged(float newValue)
{
Value = Mathf.Clamp(newValue, Min, Max);
HudManager.Instance.Notifier.AddSettingsChangeMessage(
StringName,
Data?.GetValueString(Value),
false);
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Value), false);
if (OptionBehaviour is NumberOption opt)
if (!OptionBehaviour)
{
opt.Value = Value;
return;
}
var opt = OptionBehaviour as NumberOption;
if (opt)
{
opt.Value = newValue;
}
}
/// <summary>
/// Implicitly converts the option to an int.
/// </summary>
/// <param name="option">The option.</param>
/// <returns>Integer value.</returns>
public static implicit operator int(ModdedNumberOption option)
{
return (int)option.Value;
}
}
}
+30 -145
View File
@@ -1,109 +1,41 @@
using BepInEx.Configuration;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using Reactor.Localization.Utilities;
using Reactor.Networking.Rpc;
using System;
using BepInEx.Configuration;
using MiraAPI.Networking;
using Reactor.Networking.Rpc;
using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// Represents a modded option.
/// </summary>
/// <typeparam name="T">The value type.</typeparam>
public abstract class ModdedOption<T> : IModdedOption
{
private IMiraPlugin? _parentMod;
/// <summary>
/// Gets the unique identifier of the option.
/// </summary>
private IMiraPlugin _parentMod;
public uint Id { get; }
/// <summary>
/// Gets the title of the option.
/// </summary>
public string Title { get; }
/// <summary>
/// Gets the StringName object of the option.
/// </summary>
public StringNames StringName { get; }
/// <summary>
/// Gets the BaseGameSetting data of the option.
/// </summary>
public BaseGameSetting Data { get; protected init; }
/// <summary>
/// Gets or sets the parent mod of the option.
/// </summary>
public IMiraPlugin? ParentMod
public IMiraPlugin ParentMod
{
get => _parentMod;
set
{
if (_parentMod != null || value == null) return;
_parentMod = value;
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
Value = entry.Value;
}
}
/// <summary>
/// Gets or sets the value of the option.
/// </summary>
public T Value { get; protected set; }
/// <summary>
/// Gets the default value of the option.
/// </summary>
public T DefaultValue { get; }
/// <summary>
/// Gets or sets the event that is invoked when the value of the option changes.
/// </summary>
public Action<T>? ChangedEvent { get; set; }
/// <summary>
/// Gets or sets the visibility of the option.
/// </summary>
public T DefaultValue { get; init; }
public Action<T> ChangedEvent { get; set; }
public string Title { get; }
public StringNames StringName { get; }
public Func<bool> Visible { get; set; }
public Type AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; set; }
public ConfigDefinition ConfigDefinition => new ("Options", Title);
/// <summary>
/// Gets or sets the advanced role of the option.
/// </summary>
public Type? AdvancedRole { get; set; }
/// <summary>
/// Gets or sets the option behaviour of the option.
/// </summary>
public OptionBehaviour? OptionBehaviour { get; protected set; }
/// <summary>
/// Gets or sets the config definition of the option.
/// </summary>
public ConfigDefinition? ConfigDefinition
{
get => _configDefinition;
set
{
if (_configDefinition is not null) return;
_configDefinition = value;
}
}
private ConfigDefinition? _configDefinition;
/// <summary>
/// Initializes a new instance of the <see cref="ModdedOption{T}"/> class.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="roleType">The Role Type or null if it doesn't belong to a role.</param>
protected ModdedOption(string title, T defaultValue, Type? roleType)
protected ModdedOption(string title, T defaultValue, Type roleType)
{
Id = ModdedOptionsManager.NextId;
Title = title;
@@ -118,90 +50,43 @@ public abstract class ModdedOption<T> : IModdedOption
}
}
internal void ValueChanged(OptionBehaviour optionBehaviour)
public void ValueChanged(OptionBehaviour optionBehaviour)
{
SetValue(GetValueFromOptionBehaviour(optionBehaviour));
}
/// <summary>
/// Sets the value of the option.
/// </summary>
/// <param name="newValue">The new value.</param>
public void SetValue(T newValue)
{
var oldVal = Value;
Value = newValue;
if (Value?.Equals(oldVal) == false)
if (!Value.Equals(oldVal) && ChangedEvent != null)
{
ChangedEvent?.Invoke(Value);
ChangedEvent.Invoke(Value);
}
if (AmongUsClient.Instance.AmHost)
{
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
{
entry.Value = Value;
}
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
}
if (ParentMod is not null &&
ParentMod.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry))
{
entry.Value = Value;
}
OnValueChanged(newValue);
}
/// <summary>
/// Gets the float data of the option.
/// </summary>
/// <returns>A float object representing the option's value.</returns>
public abstract float GetFloatData();
/// <summary>
/// Gets the net data of the option.
/// </summary>
/// <returns>A NetData object representing this option's data.</returns>
public abstract NetData GetNetData();
/// <summary>
/// Handles incoming net data.
/// </summary>
/// <param name="data">The NetData's byte array.</param>
public abstract void HandleNetData(byte[] data);
/// <summary>
/// Handles the value changed event.
/// </summary>
/// <param name="newValue">The new value.</param>
protected abstract void OnValueChanged(T newValue);
public abstract void OnValueChanged(T newValue);
/// <summary>
/// Gets the value from the option behaviour.
/// </summary>
/// <param name="optionBehaviour">The OptionBehaviour.</param>
/// <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>
public abstract OptionBehaviour CreateOption(
ToggleOption toggleOpt,
NumberOption numberOpt,
StringOption stringOpt,
Transform container);
public abstract OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
/// <summary>
/// Implicitly converts the option to type of <typeparamref name="T"/>.
/// </summary>
/// <param name="option">The option.</param>
/// <returns>Value of type <typeparamref name="T"/>.</returns>
public static implicit operator T(ModdedOption<T> option)
{
return option.Value;
}
}
public abstract string GetHudStringText();
}
@@ -0,0 +1,77 @@
using Reactor.Localization.Utilities;
using System;
using System.Linq;
using MiraAPI.Networking;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
public class ModdedStringOption : ModdedOption<int>
{
public string[] Values { get; }
public ModdedStringOption(string title, int defaultValue, string[] values, Type roleType=null) : base(title, defaultValue, roleType)
{
Values = values;
Data = ScriptableObject.CreateInstance<StringGameSetting>();
var data = (StringGameSetting)Data;
data.Title = StringName;
data.Type = global::OptionTypes.String;
data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Value;
}
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var stringOption = Object.Instantiate(stringOpt, container);
stringOption.SetUpFromData(Data, 20);
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
// SetUpFromData method doesnt work correctly so we must set the values manually
stringOption.Title = StringName;
stringOption.Values = ((StringGameSetting)Data).Values;
stringOption.Value = Value;
OptionBehaviour = stringOption;
return stringOption;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
}
public override void HandleNetData(byte[] data)
{
SetValue(BitConverter.ToInt32(data));
}
public override string GetHudStringText()
{
return Title + ": " + Values[Value];
}
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return optionBehaviour.GetInt();
}
public override void OnValueChanged(int newValue)
{
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
if (!OptionBehaviour)
{
return;
}
var opt = OptionBehaviour as StringOption;
if (opt)
{
opt.Value = newValue;
}
}
}
@@ -5,18 +5,9 @@ using Object = UnityEngine.Object;
namespace MiraAPI.GameOptions.OptionTypes;
/// <summary>
/// A modded toggle option.
/// </summary>
public class ModdedToggleOption : ModdedOption<bool>
{
/// <summary>
/// Initializes a new instance of the <see cref="ModdedToggleOption"/> class.
/// </summary>
/// <param name="title">The option title.</param>
/// <param name="defaultValue">The default value.</param>
/// <param name="roleType">An optional role type.</param>
public ModdedToggleOption(string title, bool defaultValue, Type? roleType = null) : base(title, defaultValue, roleType)
public ModdedToggleOption(string title, bool defaultValue, Type roleType = null) : base(title, defaultValue, roleType)
{
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
@@ -25,7 +16,6 @@ public class ModdedToggleOption : ModdedOption<bool>
data.Type = global::OptionTypes.Checkbox;
}
/// <inheritdoc />
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
{
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
@@ -42,42 +32,38 @@ public class ModdedToggleOption : ModdedOption<bool>
return toggleOption;
}
/// <inheritdoc />
public override float GetFloatData()
{
return Value ? 1 : 0;
}
/// <inheritdoc />
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
}
/// <inheritdoc />
public override void HandleNetData(byte[] data)
{
SetValue(BitConverter.ToBoolean(data));
}
/// <inheritdoc />
public override string GetHudStringText()
{
return Title + ": " + (Value ? "On" : "Off");
}
public override bool GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
{
return optionBehaviour.GetBool();
}
/// <inheritdoc />
protected override void OnValueChanged(bool newValue)
public override void OnValueChanged(bool newValue)
{
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false);
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false);
if (!OptionBehaviour)
{
return;
}
if (OptionBehaviour is ToggleOption toggleOpt)
var toggleOpt = OptionBehaviour as ToggleOption;
if (toggleOpt)
{
toggleOpt.CheckMark.enabled = newValue;
}
}
}
}
-17
View File
@@ -1,17 +0,0 @@
namespace MiraAPI.Hud;
/// <summary>
/// The location of the custom button on the screen.
/// </summary>
public enum ButtonLocation
{
/// <summary>
/// Placed in the bottom left.
/// </summary>
BottomLeft,
/// <summary>
/// Placed in the bottom right.
/// </summary>
BottomRight,
}
+62 -332
View File
@@ -1,95 +1,67 @@
using MiraAPI.Events;
using MiraAPI.Events.Mira;
using MiraAPI.Patches;
using MiraAPI.Utilities.Assets;
using System;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Hud;
/// <summary>
/// Class for making custom action buttons. More customizable than the default Action/Ability buttons in the base game.
/// </summary>
public abstract class CustomActionButton
{
/// <summary>
/// Gets the name and text of the button.
/// The name and text of the button.
/// </summary>
public abstract string Name { get; }
/// <summary>
/// Gets the initial cooldown duration in seconds.
/// </summary>
public virtual float InitialCooldown => Cooldown;
/// <summary>
/// Gets the button's cooldown duration in seconds.
/// How long the button takes to cooldown.
/// </summary>
public abstract float Cooldown { get; }
/// <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.
/// If the button has an effect ability, how long the effect should last. To disable effects, set to 0.
/// </summary>
public abstract float EffectDuration { get; }
/// <summary>
/// If the button has limited uses, how many uses it has. To make the button infinite, set to 0.
/// </summary>
public abstract int MaxUses { get; }
/// <summary>
/// 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.
/// </summary>
public virtual float EffectDuration => 0;
/// <summary>
/// Gets the maximum amount of uses the button has. If the button has infinite uses, set to 0.
/// </summary>
public virtual int MaxUses => 0;
/// <summary>
/// Gets the location of the button on the screen.
/// </summary>
public virtual ButtonLocation Location { get; private set; } = ButtonLocation.BottomLeft;
/// <summary>
/// Gets a value indicating whether the button has an effect ability.
/// Returns true if the button has an effect ability.
/// </summary>
public bool HasEffect => EffectDuration > 0;
/// <summary>
/// Gets a value indicating whether the button has limited uses.
/// Returns true if the button has limited uses.
/// </summary>
public bool LimitedUses => MaxUses > 0;
/// <summary>
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
/// Returns true if the effect is currently active.
/// </summary>
public bool EffectActive { get; protected set; }
protected bool EffectActive;
/// <summary>
/// Gets or sets a value indicating whether the timer is currently active.
/// A timer variable to measure cooldowns and effects.
/// </summary>
public bool TimerPaused { get; protected set; }
protected float Timer;
/// <summary>
/// Gets or sets the amount of uses left.
/// Returns the amount of uses left.
/// </summary>
public int UsesLeft { get; protected set; }
protected int UsesLeft;
/// <summary>
/// Gets or sets the timer variable to measure cooldowns and effects.
/// The button object in game. This is created by Mira API automatically.
/// </summary>
public float Timer { get; protected set; }
/// <summary>
/// Gets the button object in game. This is created by Mira API automatically.
/// </summary>
protected ActionButton? Button { get; private set; }
/// <summary>
/// The method used to create the button.
/// </summary>
/// <param name="parent">The parent of the button.</param>
public virtual void CreateButton(Transform parent)
protected ActionButton Button { get; private set; }
internal void CreateButton(Transform parent)
{
if (Button)
{
@@ -97,13 +69,12 @@ public abstract class CustomActionButton
}
UsesLeft = MaxUses;
Timer = AmongUsClient.Instance?.NetworkMode == NetworkModes.FreePlay ? 0 : InitialCooldown;
Timer = Cooldown;
EffectActive = false;
TimerPaused = false;
Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent);
Button.name = Name + "Button";
Button.OverrideText(Name.ToUpperInvariant());
Button.OverrideText(Name.ToUpper());
Button.graphic.sprite = Sprite.LoadAsset();
@@ -114,198 +85,50 @@ public abstract class CustomActionButton
}
var pb = Button.GetComponent<PassiveButton>();
pb.OnClick = new Button.ButtonClickedEvent();
pb.OnClick.AddListener((UnityAction)(() =>
{
// Invoke the generic button click event.
var genericEvent = new MiraButtonClickEvent(this);
MiraEventManager.InvokeEvent(genericEvent);
if (genericEvent.IsCancelled)
{
MiraEventManager.InvokeEvent(new MiraButtonCancelledEvent(this));
}
// Invoke the button click event for specific button.
var eventType = CustomButtonManager.ButtonEventTypes[GetType()];
var @event = (MiraCancelableEvent)Activator.CreateInstance(eventType, this, genericEvent)!;
var specificInvoked = MiraEventManager.InvokeEvent(@event, eventType);
if (@event.IsCancelled)
{
var cancelEventType = CustomButtonManager.ButtonCancelledEventTypes[GetType()];
var cancelEvent = (MiraEvent)Activator.CreateInstance(cancelEventType, this)!;
MiraEventManager.InvokeEvent(cancelEvent, cancelEventType);
}
if (specificInvoked)
{
if (!@event.IsCancelled)
{
ClickHandler();
}
}
else
{
if (!genericEvent.IsCancelled)
{
ClickHandler();
}
}
}));
pb.OnClick.RemoveAllListeners();
pb.OnClick.AddListener((UnityAction)ClickHandler);
}
/// <summary>
/// Allows you to change the button's location.
/// A utility function to reset the cooldown of the button.
/// </summary>
/// <param name="location">The new location.</param>
/// <param name="moveButton">Whether the button's position should change ingame.</param>
public virtual void SetButtonLocation(ButtonLocation location, bool moveButton = true)
{
if (!HudManager.InstanceExists || Button == null)
{
return;
}
this.Location = location;
if (!moveButton) return;
if (HudManagerPatches._bottomLeft == null || HudManagerPatches._bottomRight == null)
{
return;
}
switch (location)
{
case ButtonLocation.BottomLeft:
var gridArrange = HudManagerPatches._bottomLeft.GetComponent<GridArrange>();
var aspectPosition = HudManagerPatches._bottomLeft.GetComponent<AspectPosition>();
Button.transform.SetParent(HudManagerPatches._bottomLeft.transform);
gridArrange.Start();
gridArrange.ArrangeChilds();
aspectPosition.AdjustPosition();
break;
case ButtonLocation.BottomRight:
Button.transform.SetParent(HudManagerPatches._bottomRight);
break;
}
}
/// <summary>
/// A utility function to reset the cooldown and/or effect of the button.
/// </summary>
public virtual void ResetCooldownAndOrEffect()
public void ResetCooldown()
{
Timer = Cooldown;
if (EffectActive)
{
OnEffectEnd();
}
EffectActive = false;
}
/// <summary>
/// A utility function to override the sprite of the button.
/// </summary>
/// <param name="sprite">The new sprite to override with.</param>
public virtual void OverrideSprite(Sprite sprite)
/// <param name="sprite">The new sprite to override with</param>
public void OverrideSprite(Sprite sprite)
{
if (Button != null)
{
Button.graphic.sprite = sprite;
}
Button.graphic.sprite = sprite;
}
/// <summary>
/// A utility function to override the name of the button.
/// </summary>
/// <param name="name">The new name to override with.</param>
public virtual void OverrideName(string name)
/// <param name="name">The new name to override with</param>
public void OverrideName(string name)
{
Button?.OverrideText(name);
Button.OverrideText(name);
}
/// <summary>
/// Set the button's timer.
/// A utility function that runs with the local PlayerControl's FixedUpdate.
/// </summary>
/// <param name="time">The time you want to set to.</param>
public virtual void SetTimer(float time)
{
Timer = Mathf.Clamp(time, -1, float.MaxValue);
}
/// <param name="playerControl">the local PlayerControl</param>
protected virtual void FixedUpdate(PlayerControl playerControl) { }
/// <summary>
/// Increase the button's timer.
/// </summary>
/// <param name="amount">The amount you want to increase by.</param>
public virtual void IncreaseTimer(float amount)
{
SetTimer(Timer + amount);
}
/// <summary>
/// Decrease the button's timer.
/// </summary>
/// <param name="amount">The amount you want to decrease by.</param>
public virtual void DecreaseTimer(float amount)
{
SetTimer(Timer - amount);
}
/// <summary>
/// Sets whether the timer is paused or not.
/// </summary>
/// <param name="val">Whether you want to pause/resume the timer.</param>
public virtual void SetTimerPaused(bool val)
{
TimerPaused = val;
}
/// <summary>
/// Set the amount of uses this button has left.
/// </summary>
/// <param name="amount">The amount you want to set to.</param>
public virtual void SetUses(int amount)
{
UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue);
Button?.SetUsesRemaining(UsesLeft);
if (Button != null)
{
Button.usesRemainingSprite.color = UsesLeft == 0 ? Color.red : Color.white;
}
}
/// <summary>
/// Increase the amount of uses this button has left.
/// </summary>
/// <param name="amount">The amount you want to increase by. Default: 1.</param>
public virtual void IncreaseUses(int amount = 1)
{
SetUses(UsesLeft + amount);
}
/// <summary>
/// Decrease the amount of uses this button has left.
/// </summary>
/// <param name="amount">The amount you want to decrease by. Default: 1.</param>
public virtual void DecreaseUses(int amount = 1)
{
SetUses(UsesLeft - amount);
}
/// <summary>
/// A utility function that runs with the local PlayerControl's FixedUpdate if the button is enabled.
/// </summary>
/// <param name="playerControl">the local PlayerControl.</param>
protected virtual void FixedUpdate(PlayerControl playerControl)
{
}
/// <summary>
/// Callback method for the button click event.
/// Runs when the button is clicked.
/// </summary>
protected abstract void OnClick();
@@ -314,36 +137,22 @@ public abstract class CustomActionButton
/// True means the button is active, false means the button is disabled.
/// </summary>
/// <param name="role">The role of the local player.</param>
/// <returns>True if the button is enabled, false otherwise.</returns>
public abstract bool Enabled(RoleBehaviour? role);
public abstract bool Enabled(RoleBehaviour role);
/// <summary>
/// Given that there is an effect, this method runs when the effect ends.
/// <br /> <br /> THIS IS A CALLBACK METHOD! Use <see cref="ResetCooldownAndOrEffect" /> if you want to end the effect.
/// Given that there is an effect, this method that runs when the effect ends.
/// </summary>
public virtual void OnEffectEnd()
{
}
public virtual void OnEffectEnd() { }
/// <summary>
/// When the button is usable, this method is called to determine if the button can be clicked.
/// When the button is enabled, this method is called to determine if the button can be used.
/// By default, it takes into account the timer, effect, and uses.
/// You can override it to change the behavior.
/// </summary>
/// <returns>A value that represents whether the button can be clicked or not.</returns>
public virtual bool CanClick()
{
return Timer <= 0 && !EffectActive && CanUse();
}
/// <summary>
/// Whether the button should light up or not. This is also the base for CanClick.
/// You can override it to change the behaviour. Do not include timer in here, that is for CanClick.
/// </summary>
/// <returns>A value that represents whether the button should light up or not.</returns>
/// <returns>A value that represents whether the button can be used or not.</returns>
public virtual bool CanUse()
{
return !LimitedUses || UsesLeft > 0;
return Timer <= 0 && !EffectActive && (!LimitedUses || UsesLeft > 0);
}
/// <summary>
@@ -351,11 +160,11 @@ public abstract class CustomActionButton
/// The default behavior is to show the button if the <paramref name="visible"/> parameter and the <see cref="Enabled"/> method return true.
/// It can be overridden for custom behavior.
/// </summary>
/// <param name="visible">Passed in from HudManager.SetHudActive, should hud be active.</param>
/// <param name="visible">Passed in from HudManager.SetHudActive, should hud be active?</param>
/// <param name="role">Passed in from HudManager.SetHudActive, the current role of the player.</param>
public virtual void SetActive(bool visible, RoleBehaviour role)
{
Button?.ToggleVisible(visible && Enabled(role));
Button.ToggleVisible(visible && Enabled(role));
}
/// <summary>
@@ -365,7 +174,7 @@ public abstract class CustomActionButton
/// </summary>
public virtual void ClickHandler()
{
if (!CanClick())
if (!CanUse())
{
return;
}
@@ -373,11 +182,11 @@ public abstract class CustomActionButton
if (LimitedUses)
{
UsesLeft--;
Button?.SetUsesRemaining(UsesLeft);
Button.SetUsesRemaining(UsesLeft);
}
OnClick();
Button.SetDisabled();
if (HasEffect)
{
EffectActive = true;
@@ -394,10 +203,10 @@ public abstract class CustomActionButton
/// By default, it handles the cooldown and effect timers, and sets the button to enabled or disabled.
/// It can be overridden for custom behavior.
/// </summary>
/// <param name="playerControl">The local PlayerControl.</param>
/// <param name="playerControl">The local PlayerControl</param>
public virtual void FixedUpdateHandler(PlayerControl playerControl)
{
if (Timer >= 0 && !TimerPaused)
if (Timer >= 0)
{
Timer -= Time.deltaTime;
}
@@ -410,94 +219,15 @@ public abstract class CustomActionButton
if (CanUse())
{
Button?.SetEnabled();
Button.SetEnabled();
}
else
{
Button?.SetDisabled();
}
if (EffectActive)
{
Button?.SetFillUp(Timer, EffectDuration);
Button!.cooldownTimerText.text = Mathf.CeilToInt(Timer).ToString();
Button!.cooldownTimerText.gameObject.SetActive(true);
}
else
{
Button?.SetCoolDown(Timer, Cooldown);
Button.SetDisabled();
}
Button.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown);
FixedUpdate(playerControl);
}
}
/// <summary>
/// Custom action button that has a target object.
/// </summary>
/// <typeparam name="T">The type of the target object.</typeparam>
public abstract class CustomActionButton<T> : CustomActionButton where T : MonoBehaviour
{
/// <summary>
/// Gets or sets the target object of the button.
/// </summary>
public T? Target { get; protected set; }
/// <summary>
/// Gets the distance the player must be from the target object to use the button.
/// </summary>
public virtual float Distance => PlayerControl.LocalPlayer.Data.Role.GetAbilityDistance();
/// <summary>
/// Determines if the target object is valid.
/// </summary>
/// <param name="target">The target object being checked.</param>
/// <returns>True if the target object is valid, false otherwise.</returns>
public virtual bool IsTargetValid(T? target)
{
return target != null;
}
/// <summary>
/// The method used to get the target object.
/// </summary>
/// <returns>The target object or null if it isn't found.</returns>
public abstract T? GetTarget();
/// <summary>
/// Sets the outline of the target object.
/// </summary>
/// <param name="active">Should the outline be active.</param>
public abstract void SetOutline(bool active);
/// <inheritdoc />
public override bool CanUse()
{
var newTarget = GetTarget();
if (newTarget != Target)
{
SetOutline(false);
}
Target = IsTargetValid(newTarget) ? newTarget : null;
SetOutline(true);
return base.CanUse() && Target != null;
}
/// <inheritdoc />
public override bool CanClick()
{
return base.CanClick() && Target != null;
}
/// <summary>
/// Use this to reset the button's target after used.
/// </summary>
public virtual void ResetTarget()
{
Target = null;
SetOutline(false);
}
}
}
+14 -25
View File
@@ -1,42 +1,31 @@
using MiraAPI.Events.Mira;
using MiraAPI.PluginLoading;
using System;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Reflection;
using Reactor.Utilities;
namespace MiraAPI.Hud;
/// <summary>
/// Custom button manager for handling custom buttons.
/// </summary>
public static class CustomButtonManager
{
/// <summary>
/// Gets a list of all registered custom buttons.
/// </summary>
public static ReadOnlyCollection<CustomActionButton> Buttons { get; internal set; } = new([]);
public static readonly List<CustomActionButton> CustomButtons = [];
internal static readonly List<CustomActionButton> CustomButtons = [];
internal static readonly Dictionary<Type, Type> ButtonEventTypes = [];
internal static readonly Dictionary<Type, Type> ButtonCancelledEventTypes = [];
internal static bool RegisterButton(Type buttonType, MiraPluginInfo pluginInfo)
internal static void RegisterButton(Type buttonType)
{
if (!buttonType.IsAssignableTo(typeof(CustomActionButton)) || Activator.CreateInstance(buttonType) is not CustomActionButton button)
if (!typeof(CustomActionButton).IsAssignableFrom(buttonType))
{
return false;
Logger<MiraApiPlugin>.Error($"{buttonType?.Name} does not inherit from CustomActionButton.");
return;
}
if (Activator.CreateInstance(buttonType) is not CustomActionButton button)
{
Logger<MiraApiPlugin>.Error($"Failed to create button from {buttonType.Name}");
return;
}
CustomButtons.Add(button);
pluginInfo.Buttons.Add(button);
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
.SetValue(null, button);
ButtonEventTypes.Add(buttonType, typeof(MiraButtonClickEvent<>).MakeGenericType(buttonType));
ButtonCancelledEventTypes.Add(buttonType, typeof(MiraButtonCancelledEvent<>).MakeGenericType(buttonType));
return true;
}
}
}
+23 -9
View File
@@ -1,4 +1,6 @@
using System.Linq;
#nullable enable
using System;
using System.Linq;
namespace MiraAPI.Hud;
@@ -6,14 +8,26 @@ namespace MiraAPI.Hud;
/// A utility class to get the instance of a custom action button.
/// </summary>
/// <typeparam name="T">The type of the button you are trying to access.</typeparam>
public static class CustomButtonSingleton<T> where T : CustomActionButton
public class CustomButtonSingleton<T> where T : CustomActionButton
{
private static T? _instance;
/// <summary>
/// Gets the instance of the button.
/// </summary>
#pragma warning disable CA1000
public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
#pragma warning restore CA1000
}
public static T Instance
{
get => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
set
{
if (_instance == value)
{
return;
}
if (_instance != null)
{
throw new InvalidOperationException($"Instance for {typeof(T)} is already set");
}
_instance = value;
}
}
}
-119
View File
@@ -1,119 +0,0 @@
using AmongUs.GameOptions;
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Patches.Stubs;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
namespace MiraAPI.Hud;
/// <summary>
/// Custom Player Menu using the ShapeshifterPanel as a base.
/// </summary>
/// <param name="il2CppPtr">Used by Il2Cpp. Do not use constructor, this is a MonoBehaviour.</param>
[RegisterInIl2Cpp]
public class CustomPlayerMenu(IntPtr il2CppPtr) : Minigame(il2CppPtr)
{
public ShapeshifterPanel PanelPrefab;
public float XStart = -0.8f;
public float YStart = 2.15f;
public float XOffset = 1.95f;
public float YOffset = -0.65f;
public UiElement BackButton;
public UiElement DefaultButtonSelected;
public List<ShapeshifterPanel> potentialVictims;
/// <summary>
/// Creates a CustomPlayerMenu.
/// </summary>
/// <returns>New CustomPlayerMenu object.</returns>
public static CustomPlayerMenu Create()
{
var shapeShifterRole = RoleManager.Instance.GetRole(RoleTypes.Shapeshifter);
var ogMenu = shapeShifterRole.TryCast<ShapeshifterRole>().ShapeshifterMenu;
var newMenu = Instantiate(ogMenu);
var customMenu = newMenu.gameObject.AddComponent<CustomPlayerMenu>();
customMenu.PanelPrefab = newMenu.PanelPrefab;
customMenu.XStart = newMenu.XStart;
customMenu.YStart = newMenu.YStart;
customMenu.XOffset = newMenu.XOffset;
customMenu.YOffset = newMenu.YOffset;
customMenu.BackButton = newMenu.BackButton;
var back = customMenu.BackButton.GetComponent<PassiveButton>();
back.OnClick.RemoveAllListeners();
back.OnClick.AddListener((UnityAction)(() =>
{
Instance.Close();
}));
customMenu.CloseSound = newMenu.CloseSound;
customMenu.logger = newMenu.logger;
customMenu.OpenSound = newMenu.OpenSound;
newMenu.DestroyImmediate();
customMenu.transform.SetParent(Camera.main.transform, false);
customMenu.transform.localPosition = new Vector3(0f, 0f, -50f);
return customMenu;
}
private void OnDisable()
{
ControllerManager.Instance.CloseOverlayMenu(name);
}
public override void Begin(PlayerTask task)
{
throw new NotImplementedException("Use the other Begin method.");
}
/// <summary>
/// Begins/opens the custom player menu.
/// </summary>
/// <param name="playerMatch">Function to determine if player should show in the custom menu.</param>
/// <param name="onClick">Onclick action for player.</param>
[HideFromIl2Cpp]
public void Begin(Func<PlayerControl, bool> playerMatch, Action<PlayerControl?> onClick)
{
MinigameStubs.Begin(this, null);
var back = BackButton.GetComponent<PassiveButton>();
back.OnClick.AddListener((UnityAction)(() =>
{
onClick(null);
}));
DestroyableSingleton<DebugAnalytics>.Instance.Analytics.MinigameOpened(PlayerControl.LocalPlayer.Data, TaskType);
var list = PlayerControl.AllPlayerControls.ToArray().Where(playerMatch).ToList();
potentialVictims = [];
var list2 = new Il2CppSystem.Collections.Generic.List<UiElement>();
for (var i = 0; i < list.Count; i++)
{
var player = list[i];
var num = i % 3;
var num2 = i / 3;
var flag = PlayerControl.LocalPlayer.Data.Role.NameColor == player.Data.Role.NameColor;
var shapeshifterPanel = Instantiate(PanelPrefab, transform);
shapeshifterPanel.transform.localPosition = new Vector3(XStart + num * XOffset, YStart + num2 * YOffset, -1f);
shapeshifterPanel.SetPlayer(i, player.Data, (Il2CppSystem.Action)(() => { onClick(player); }));
shapeshifterPanel.NameText.color = flag ? player.Data.Role.NameColor : Color.white;
potentialVictims.Add(shapeshifterPanel);
list2.Add(shapeshifterPanel.Button);
}
ControllerManager.Instance.OpenOverlayMenu(name, BackButton, DefaultButtonSelected, list2);
}
}
+9
View File
@@ -0,0 +1,9 @@
using System;
namespace MiraAPI.Hud;
/// <summary>
/// Attribute to register a button in the HUD. Necessary for the button to be recognized by Mira.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class RegisterButtonAttribute : Attribute;
+24 -8
View File
@@ -1,20 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<DebugType>embedded</DebugType>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Description>Simple and easy to use Among Us modding API.</Description>
<PackageId>AllOfUs.MiraAPI</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Angxl, XtraCube</Authors>
</PropertyGroup>
<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="\" />
<EmbeddedResource Include="Resources\Cog.png" />
<EmbeddedResource Include="Resources\CogActive.png" />
<EmbeddedResource Include="Resources\NextButton.png" />
<EmbeddedResource Include="Resources\NextButtonActive.png" />
<EmbeddedResource Include="Resources\Empty.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\**\*.*" />
<PackageReference Include="Reactor" Version="2.3.0" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="Iced" Version="1.21.0" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.8.13" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<Import Project="../AmongUs.props" />
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" />
</Target>
</Project>
+22 -6
View File
@@ -3,9 +3,11 @@ using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
using Reactor;
using Reactor.Patches;
using System;
using System.Text;
using Reactor.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI;
@@ -16,18 +18,32 @@ namespace MiraAPI;
[ReactorModFlags(ModFlags.RequireOnAllClients)]
public partial class MiraApiPlugin : BasePlugin
{
private static MiraPluginManager? PluginManager { get; set; }
public Harmony Harmony { get; } = new(Id);
public static Color MiraColor { get; } = new Color32(238, 154, 112, 255);
private static MiraPluginManager PluginManager { get; set; }
public static Color MiraColor = new Color32(238, 154, 112, 255);
public override void Load()
{
Harmony.PatchAll();
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
PluginManager = new MiraPluginManager();
PluginManager.Initialize();
ReactorVersionShower.TextUpdated += text =>
{
text.text = new StringBuilder($"{MiraColor.ToTextColor()}Mira API</color> ")
.Append(GetShortHashVersion(Version))
.Append('\n')
.Append(text.text)
.ToString();
};
}
private static string GetShortHashVersion(string version)
{
var index = version.IndexOf("+", StringComparison.Ordinal);
return index < 0 ? version : version[..(index + 3)];
}
}
-80
View File
@@ -1,80 +0,0 @@
namespace MiraAPI.Modifiers;
/// <summary>
/// Base class for all modifiers.
/// </summary>
public abstract class BaseModifier
{
/// <summary>
/// Gets the player that the modifier is attached to.
/// </summary>
public PlayerControl? Player { get; internal set; }
/// <summary>
/// Gets the modifier component that the modifier is attached to.
/// </summary>
public ModifierComponent? ModifierComponent { get; internal set; }
/// <summary>
/// Gets the modifier id.
/// </summary>
public uint ModifierId { get; internal set; }
/// <summary>
/// Gets the modifier name.
/// </summary>
public abstract string ModifierName { get; }
/// <summary>
/// Gets a value indicating whether the modifier is hidden on the UI.
/// </summary>
public virtual bool HideOnUi => false;
/// <summary>
/// Gets the HUD information for this modifier. Defaults to the modifier name. Does nothing if <see cref="HideOnUi"/> is true.
/// </summary>
/// <returns>The information string for the HUD.</returns>
public virtual string GetHudString() => ModifierName;
/// <summary>
/// Called when the modifier is activated.
/// </summary>
public virtual void OnActivate()
{
}
/// <summary>
/// Called when the modifier is deactivated.
/// </summary>
public virtual void OnDeactivate()
{
}
/// <summary>
/// Called when the modifier is updated. Attached to the ModifierComponent's Update method.
/// </summary>
public virtual void Update()
{
}
/// <summary>
/// Called when the modifier is updated. Attached to the ModifierComponent's FixedUpdate method.
/// </summary>
public virtual void FixedUpdate()
{
}
/// <summary>
/// Called when the player dies.
/// </summary>
/// <param name="reason">The Death Reason.</param>
public virtual void OnDeath(DeathReason reason)
{
}
/// <summary>
/// Determines whether the player can vent.
/// </summary>
/// <returns>True if the player can vent, false otherwise. Null for no effect.</returns>
public virtual bool? CanVent() => null;
}
-278
View File
@@ -1,278 +0,0 @@
using Il2CppInterop.Runtime.Attributes;
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Globalization;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers;
/// <summary>
/// The component for handling modifiers.
/// </summary>
[RegisterInIl2Cpp]
public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
{
/// <summary>
/// Gets the active modifiers on the player.
/// </summary>
[HideFromIl2Cpp]
public ImmutableList<BaseModifier> ActiveModifiers => Modifiers.ToImmutableList();
[HideFromIl2Cpp]
private List<BaseModifier> Modifiers { get; set; } = [];
private PlayerControl? _player;
private TextMeshPro? _modifierText;
private readonly List<BaseModifier> _toRemove = [];
private readonly List<BaseModifier> _toAdd = [];
internal void ClearModifiers()
{
_toRemove.AddRange(Modifiers);
}
private void Start()
{
_player = GetComponent<PlayerControl>();
Modifiers = [];
if (!_player.AmOwner)
{
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;
}
private void FixedUpdate()
{
foreach (var modifier in _toRemove)
{
modifier.OnDeactivate();
Modifiers.Remove(modifier);
}
foreach (var modifier in _toAdd)
{
Modifiers.Add(modifier);
modifier.OnActivate();
if (modifier is TimedModifier { AutoStart: true } timer)
{
timer.StartTimer();
}
}
if (_toAdd.Count > 0 || _toRemove.Count > 0)
{
if (_player?.AmOwner == true)
{
HudManager.Instance?.SetHudActive(_player, _player.Data.Role, HudManager.Instance.TaskPanel.isActiveAndEnabled);
}
_toAdd.Clear();
_toRemove.Clear();
}
foreach (var modifier in Modifiers)
{
modifier.FixedUpdate();
}
}
private void Update()
{
foreach (var modifier in Modifiers)
{
modifier.Update();
}
if (!_modifierText || _player?.AmOwner == false)
{
return;
}
var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi);
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
if (baseModifiers.Length != 0 && !MeetingHud.Instance)
{
var stringBuild = new StringBuilder();
foreach (var mod in baseModifiers)
{
stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}");
}
_modifierText!.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
}
else if (_modifierText!.text != string.Empty)
{
_modifierText.text = string.Empty;
}
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="type">The modifier type.</param>
[HideFromIl2Cpp]
public void RemoveModifier(Type type)
{
var modifier = Modifiers.Find(x => x.GetType() == type);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
RemoveModifier(modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <typeparam name="T">The modifier type.</typeparam>
[HideFromIl2Cpp]
public void RemoveModifier<T>() where T : BaseModifier
{
RemoveModifier(typeof(T));
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifierId">The modifier ID.</param>
[HideFromIl2Cpp]
public void RemoveModifier(uint modifierId)
{
var modifier = Modifiers.Find(x => x.ModifierId == modifierId);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not active.");
return;
}
RemoveModifier(modifier);
}
/// <summary>
/// Removes a modifier from the player.
/// </summary>
/// <param name="modifier">The modifier object.</param>
[HideFromIl2Cpp]
public void RemoveModifier(BaseModifier modifier)
{
if (!Modifiers.Contains(modifier))
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
return;
}
_toRemove.Add(modifier);
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="modifier">The modifier to add.</param>
/// <returns>The modifier that was added.</returns>
[HideFromIl2Cpp]
public BaseModifier? AddModifier(BaseModifier modifier)
{
if (Modifiers.Contains(modifier))
{
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
return null;
}
var modifierId = ModifierManager.GetModifierId(modifier.GetType());
if (modifierId == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {modifier.GetType().Name} because it has no ID!");
return null;
}
_toAdd.Add(modifier);
modifier.Player = _player;
modifier.ModifierComponent = this;
modifier.ModifierId = modifierId.Value;
return modifier;
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <param name="type">The modifier type.</param>
/// <returns>The modifier that was added.</returns>
[HideFromIl2Cpp]
public BaseModifier? AddModifier(Type type)
{
var modifierId = ModifierManager.GetModifierId(type);
if (modifierId == null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is not registered.");
return null;
}
if (Modifiers.Find(x => x.ModifierId == modifierId) != null)
{
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
return null;
}
if (Activator.CreateInstance(type) is not BaseModifier modifier)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return null;
}
AddModifier(modifier);
return modifier;
}
/// <summary>
/// Adds a modifier to the player.
/// </summary>
/// <typeparam name="T">The Type of the modifier.</typeparam>
/// <returns>The new modifier.</returns>
[HideFromIl2Cpp]
public T? AddModifier<T>() where T : BaseModifier
{
return AddModifier(typeof(T)) as T;
}
/// <summary>
/// Checks if a player has an active or queued modifier by its ID.
/// </summary>
/// <param name="id">The Modifier ID.</param>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public bool HasModifier(uint id)
{
return ActiveModifiers.Exists(x => x.ModifierId == id) || _toAdd.Exists(x => x.ModifierId == id);
}
/// <summary>
/// Checks if a player has an active or queued modifier by its type.
/// </summary>
/// <typeparam name="T">The Type of the Modifier.</typeparam>
/// <returns>True if the Modifier is present, false otherwise.</returns>
public bool HasModifier<T>()
{
return ActiveModifiers.Exists(x => x is T) || _toAdd.Exists(x => x is T);
}
}
-219
View File
@@ -1,219 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities;
using Reactor.Utilities.Extensions;
using Random = System.Random;
namespace MiraAPI.Modifiers;
/// <summary>
/// The manager for handling modifiers.
/// </summary>
public static class ModifierManager
{
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
private static uint _nextId;
private static uint GetNextId()
{
_nextId++;
return _nextId;
}
/// <summary>
/// Gets the modifier type from the id.
/// </summary>
/// <param name="id">The ID.</param>
/// <returns>The Type of the modifier.</returns>
public static Type? GetModifierType(uint id)
{
return IdToTypeModifierMap.GetValueOrDefault(id);
}
/// <summary>
/// Gets the modifier id from the type.
/// </summary>
/// <param name="type">The Type.</param>
/// <returns>The ID of the modifier.</returns>
public static uint? GetModifierId(Type type)
{
return TypeToIdModifierMap.GetValueOrDefault(type);
}
internal static bool RegisterModifier(Type modifierType)
{
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
{
return false;
}
IdToTypeModifierMap.Add(GetNextId(), modifierType);
TypeToIdModifierMap.Add(modifierType, _nextId);
if (!typeof(GameModifier).IsAssignableFrom(modifierType))
{
return true;
}
var mod = Activator.CreateInstance(modifierType) as GameModifier;
var priority = mod!.Priority();
if (!PrioritiesToIdsMap.TryGetValue(priority, out var list))
{
PrioritiesToIdsMap[priority] = list = [];
}
list.Add(_nextId);
return true;
}
internal static void AssignModifiers(List<PlayerControl> plrs)
{
var rand = new Random();
foreach (var priority in PrioritiesToIdsMap.Keys.OrderByDescending(x => x))
{
var filteredModifiers = new List<uint>();
foreach (var id in PrioritiesToIdsMap[priority])
{
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}");
continue;
}
var shuffledList = filteredModifiers.Randomize();
if (shuffledList.Count > plrs.Count)
{
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 == null)
{
Logger<MiraApiPlugin>.Warning($"Somehow valid players for modifier {mod!.ModifierName} disappeared");
}
else
{
plr.RpcAddModifier(id);
}
}
}
}
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) &&
!player.HasModifier(modifierId);
}
internal static void SyncAllModifiers(int targetId = -1)
{
var data = new List<NetData>();
foreach (var player in PlayerControl.AllPlayerControls)
{
data.Add(GetPlayerModifiers(player));
}
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]);
}
internal static void HandleSyncModifiers(NetData[] data)
{
foreach (var netData in data)
{
var ids = new uint[netData.Data.Length / 4];
Buffer.BlockCopy(netData.Data, 0, ids, 0, netData.Data.Length);
var plr = GameData.Instance.GetPlayerById((byte)netData.Id).Object;
var modifierComponent = plr.GetComponent<ModifierComponent>();
if (!modifierComponent)
{
continue;
}
modifierComponent.ClearModifiers();
foreach (var id in ids)
{
if (!IdToTypeModifierMap.TryGetValue(id, out var type))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {id} because it is not registered.");
continue;
}
modifierComponent.AddModifier(type);
}
}
}
private static NetData GetPlayerModifiers(PlayerControl? player)
{
if (player == null || !player)
{
return new NetData(0, []);
}
List<byte> bytes = [];
var modifierComponent = player.GetComponent<ModifierComponent>();
if (modifierComponent == null || !modifierComponent)
{
return new NetData(player.PlayerId, []);
}
foreach (var modifier in modifierComponent.ActiveModifiers)
{
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifierMap[modifier.GetType()]));
}
return new NetData(player.PlayerId, [.. bytes]);
}
}
-39
View File
@@ -1,39 +0,0 @@
namespace MiraAPI.Modifiers.Types;
/// <summary>
/// The base class for a game modifier. Game modifiers are applied at the start of the game on top of the player's role.
/// </summary>
public abstract class GameModifier : BaseModifier
{
/// <summary>
/// Gets the chance of the modifier being assigned to a player.
/// </summary>
/// <returns>An int value between 0 and 100 representing percent.</returns>
public abstract int GetAssignmentChance();
/// <summary>
/// Gets the amount of players that can have this modifier in a game.
/// </summary>
/// <returns>An int value greater than or equal to zero.</returns>
public abstract int GetAmountPerGame();
/// <summary>
/// Gets the priority at which the modifier will spawn. The higher the value, the higher up on the assignment list.
/// </summary>
/// <returns>An int value greater than or equal to -1.</returns>
public virtual int Priority() => -1;
/// <summary>
/// Determines whether the modifier is valid on a role.
/// </summary>
/// <param name="role">The role to be checked.</param>
/// <returns>True if the modifier is valid on the role, otherwise false.</returns>
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
/// <summary>
/// Determines whether the player won the game with this modifier.
/// </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>
public virtual bool? DidWin(GameOverReason reason) => null;
}
-133
View File
@@ -1,133 +0,0 @@
using System;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Modifiers.Types;
/// <summary>
/// The base class for all timed modifiers. Timed modifiers have a duration and can be started and stopped.
/// </summary>
public abstract class TimedModifier : BaseModifier
{
/// <summary>
/// Gets the duration of the modifier.
/// </summary>
public abstract float Duration { get; }
/// <summary>
/// Gets a value indicating whether the timer should start automatically when added.
/// </summary>
public virtual bool AutoStart => false;
/// <summary>
/// Gets a value indicating whether the modifier should be removed when the timer completes.
/// </summary>
public virtual bool RemoveOnComplete => true;
/// <summary>
/// Called when the timer completes.
/// </summary>
public abstract void OnTimerComplete();
/// <summary>
/// Gets or sets a value indicating whether the timer is active.
/// </summary>
public bool TimerActive { get; protected set; }
/// <summary>
/// Gets or sets the time remaining on the timer.
/// </summary>
public float TimeRemaining { get; protected set; }
/// <inheritdoc />
public override bool HideOnUi => !TimerActive;
/// <summary>
/// Gets the HUD information for Timed Modifier, including the time remaining.
/// </summary>
/// <returns>A string with the hud information + timer.</returns>
public override string GetHudString()
{
return base.GetHudString() + $" <size=70%>({Math.Round(Duration - TimeRemaining, 0)}s/{Duration}s)</size>";
}
/// <summary>
/// The FixedUpdate method for timed modifiers. Automatically handles the timer logic.
/// </summary>
public override void FixedUpdate()
{
if (!TimerActive)
{
return;
}
if (TimeRemaining > 0)
{
TimeRemaining -= Time.fixedDeltaTime;
}
else
{
StopTimer();
}
}
/// <summary>
/// Starts the timer.
/// </summary>
public void StartTimer()
{
if (TimerActive)
{
Logger<MiraApiPlugin>.Error("Can't start a timer that has already been started.");
return;
}
TimeRemaining = Duration;
TimerActive = true;
}
/// <summary>
/// Stops the timer and calls OnTimerComplete().
/// </summary>
public void StopTimer()
{
if (!TimerActive)
{
return;
}
TimerActive = false;
TimeRemaining = Duration;
OnTimerComplete();
if (RemoveOnComplete)
{
ModifierComponent?.RemoveModifier(ModifierId);
}
}
/// <summary>
/// Resets the timer. Does not call OnTimerComplete().
/// </summary>
public void ResetTimer()
{
TimerActive = false;
TimeRemaining = Duration;
}
/// <summary>
/// Pauses the timer.
/// </summary>
public void PauseTimer()
{
TimerActive = false;
}
/// <summary>
/// Pauses the timer.
/// </summary>
public void ResumeTimer()
{
TimerActive = true;
}
}
-282
View File
@@ -1,282 +0,0 @@
using AmongUs.GameOptions;
using Assets.CoreScripts;
using BepInEx.Unity.IL2CPP.Utils;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Gameplay;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
using Reactor.Utilities.Extensions;
using System.Collections;
using System.Linq;
using UnityEngine;
namespace MiraAPI.Networking;
/// <summary>
/// Custom murder RPCs to fix issues with default ones.
/// </summary>
public static class CustomMurderRpc
{
/// <summary>
/// Networked Custom Murder method.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The player to murder.</param>
/// <param name="didSucceed">Whether the murder was successful or not.</param>
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
[MethodRpc((uint)MiraRpc.CustomMurder, LocalHandling = RpcLocalHandling.Before, SendImmediately = true)]
public static void RpcCustomMurder(
this PlayerControl source,
PlayerControl target,
bool didSucceed = true,
bool resetKillTimer = true,
bool createDeadBody = true,
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
{
var murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
var beforeMurderEvent = new BeforeMurderEvent(source, target);
MiraEventManager.InvokeEvent(beforeMurderEvent);
if (beforeMurderEvent.IsCancelled)
{
murderResultFlags = MurderResultFlags.FailedError;
}
var murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
source.CustomMurder(
target,
murderResultFlags2,
resetKillTimer,
createDeadBody,
teleportMurderer,
showKillAnim,
playKillSound);
}
/// <summary>
/// Custom Murder method without networking. If you need a networked version, use <see cref="RpcCustomMurder"/>.
/// </summary>
/// <param name="source">The killer.</param>
/// <param name="target">The player to murder.</param>
/// <param name="resultFlags">Murder result flags.</param>
/// <param name="resetKillTimer">Should the kill timer be reset.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the killer be snapped to the dead player.</param>
/// <param name="showKillAnim">Should the kill animation be shown.</param>
/// <param name="playKillSound">Should the kill sound be played.</param>
public static void CustomMurder(
this PlayerControl source,
PlayerControl target,
MurderResultFlags resultFlags,
bool resetKillTimer = true,
bool createDeadBody = true,
bool teleportMurderer = true,
bool showKillAnim = true,
bool playKillSound = true)
{
source.isKilling = false;
var data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedError))
{
return;
}
if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) ||
(resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1))
{
target.protectedByGuardianThisRound = true;
var flag = PlayerControl.LocalPlayer.Data.Role.Role == RoleTypes.GuardianAngel;
if (flag && PlayerControl.LocalPlayer.Data.PlayerId == target.protectedByGuardianId)
{
StatsManager.Instance.IncrementStat(StringNames.StatsGuardianAngelCrewmatesProtected);
AchievementManager.Instance.OnProtectACrewmate();
}
if (source.AmOwner || flag)
{
target.ShowFailedMurder();
if (resetKillTimer)
{
source.SetKillTimer(
GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f);
}
}
else
{
target.RemoveProtection();
}
return;
}
if (!resultFlags.HasFlag(MurderResultFlags.Succeeded) &&
!resultFlags.HasFlag(MurderResultFlags.DecisionByHost))
{
return;
}
DebugAnalytics.Instance.Analytics.Kill(target.Data, source.Data);
if (source.AmOwner)
{
StatsManager.Instance.IncrementStat(
GameManager.Instance.IsHideAndSeek()
? StringNames.StatsImpostorKills_HideAndSeek
: StringNames.StatsImpostorKills);
if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted)
{
StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills);
}
if (Constants.ShouldPlaySfx() && playKillSound)
{
SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f);
}
if (resetKillTimer)
{
source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown));
}
}
UnityTelemetry.Instance.WriteMurder();
target.gameObject.layer = LayerMask.NameToLayer("Ghost");
if (target.AmOwner)
{
StatsManager.Instance.IncrementStat(StringNames.StatsTimesMurdered);
if (Minigame.Instance)
{
try
{
Minigame.Instance.Close();
Minigame.Instance.Close();
}
catch
{
// ignored
}
}
if (showKillAnim)
{
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
}
target.cosmetics.SetNameMask(false);
target.RpcSetScanner(false);
}
AchievementManager.Instance.OnMurder(
source.AmOwner,
target.AmOwner,
source.CurrentOutfitType == PlayerOutfitType.Shapeshifted,
source.shapeshiftTargetPlayerId,
target.PlayerId);
source.MyPhysics.StartCoroutine(source.KillAnimations.Random()?.CoPerformCustomKill(source, target, createDeadBody, teleportMurderer));
}
/// <summary>
/// Perform a custom kill animation.
/// </summary>
/// <param name="anim">The kill animation.</param>
/// <param name="source">The murderer.</param>
/// <param name="target">The murdered player.</param>
/// <param name="createDeadBody">Should a dead body be created.</param>
/// <param name="teleportMurderer">Should the murder be teleported.</param>
/// <returns>Coroutine.</returns>
public static IEnumerator CoPerformCustomKill(
this KillAnimation anim,
PlayerControl source,
PlayerControl target,
bool createDeadBody = true,
bool teleportMurderer = true)
{
var cam = Camera.main?.GetComponent<FollowerCamera>();
var isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
var sourcePhys = source.MyPhysics;
if (teleportMurderer)
{
KillAnimation.SetMovement(source, false);
}
KillAnimation.SetMovement(target, false);
if (isParticipant)
{
PlayerControl.LocalPlayer.isKilling = true;
source.isKilling = true;
}
DeadBody? deadBody = null;
if (createDeadBody)
{
deadBody = Object.Instantiate(GameManager.Instance.DeadBodyPrefab);
deadBody.enabled = false;
deadBody.ParentId = target.PlayerId;
deadBody.bodyRenderers.ToList().ForEach(target.SetPlayerMaterialColors);
target.SetPlayerMaterialColors(deadBody.bloodSplatter);
var vector = target.transform.position + anim.BodyOffset;
vector.z = vector.y / 1000f;
deadBody.transform.position = vector;
}
if (isParticipant)
{
if (cam != null)
{
cam.Locked = true;
}
ConsoleJoystick.SetMode_Task();
if (PlayerControl.LocalPlayer.AmOwner)
{
PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true;
}
}
target.Die(DeathReason.Kill, true);
if (teleportMurderer)
{
yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim);
sourcePhys.Animations.PlayIdleAnimation();
source.NetTransform.SnapTo(target.transform.position);
KillAnimation.SetMovement(source, true);
}
KillAnimation.SetMovement(target, true);
if (deadBody != null)
{
deadBody.enabled = true;
}
if (!isParticipant)
{
yield break;
}
if (cam != null)
{
cam.Locked = false;
}
PlayerControl.LocalPlayer.isKilling = false;
source.isKilling = false;
var afterMurderEvent = new AfterMurderEvent(source, target);
MiraEventManager.InvokeEvent(afterMurderEvent);
}
}
+2 -32
View File
@@ -1,37 +1,7 @@
namespace MiraAPI.Networking;
/// <summary>
/// Mira RPCs.
/// </summary>
public enum MiraRpc : uint
{
/// <summary>
/// Syncs the game options.
/// </summary>
SyncGameOptions,
/// <summary>
/// Syncs the role options.
/// </summary>
SyncRoleOptions,
/// <summary>
/// Adds a modifier to a player.
/// </summary>
AddModifier,
/// <summary>
/// Removes a modifier from a player.
/// </summary>
RemoveModifier,
/// <summary>
/// Syncs all modifiers at once.
/// </summary>
SyncModifiers,
/// <summary>
/// Custom Murder RPC.
/// </summary>
CustomMurder,
}
SyncRoleOptions
}
+5 -20
View File
@@ -1,25 +1,10 @@
namespace MiraAPI.Networking;
using System;
namespace MiraAPI.Networking;
/// <summary>
/// Used to network data and mark it with an ID.
/// </summary>
/// <param name="id">The ID of the data.</param>
/// <param name="data">The byte[] data.</param>
public readonly struct NetData(uint id, byte[] data)
{
/// <summary>
/// Gets the ID of the data.
/// </summary>
public uint Id { get; } = id;
/// <summary>
/// Gets the byte[] data.
/// </summary>
public byte[] Data { get; } = data;
/// <summary>
/// Gets the length of the data in bytes.
/// </summary>
/// <returns>An int representing the number of bytes this NetData takes up.</returns>
public int GetLength() => 4 + Data.Length;
}
public int GetLength() => BitConverter.GetBytes(Id).Length + Data.Length;
}
-52
View File
@@ -1,52 +0,0 @@
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncModifiers)]
internal sealed class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[]? data)
{
if (data == null)
{
writer.WritePacked(0U);
return;
}
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
{
writer.WritePacked(netData.Id);
writer.WriteBytesAndSize(netData.Data);
}
}
public override NetData[] Read(MessageReader reader)
{
var length = reader.ReadPackedUInt32();
var data = new NetData[length];
for (var i = 0; i < length; i++)
{
var dataId = reader.ReadPackedUInt32();
var bytes = reader.ReadBytesAndSize();
data[i] = new NetData(dataId, bytes);
}
return data;
}
public override void Handle(PlayerControl playerControl, NetData[]? data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
ModifierManager.HandleSyncModifiers(data ?? []);
}
}
+8 -14
View File
@@ -7,18 +7,12 @@ namespace MiraAPI.Networking;
// METHOD RPC DOESNT WORK WITH THE ARRAYS AND STUFF SO THIS IS HOW WE WILL DO IT FOR NOW
[RegisterCustomRpc((uint)MiraRpc.SyncGameOptions)]
internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
public class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[]? data)
public override void Write(MessageWriter writer, NetData[] data)
{
if (data == null)
{
writer.WritePacked(0U);
return;
}
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
{
@@ -33,21 +27,21 @@ internal sealed class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCust
var data = new NetData[length];
for (var i = 0; i < length; i++)
{
var dataId = reader.ReadPackedUInt32();
var id = reader.ReadPackedUInt32();
var bytes = reader.ReadBytesAndSize();
data[i] = new NetData(dataId, bytes);
data[i] = new NetData(id, bytes);
}
return data;
}
public override void Handle(PlayerControl playerControl, NetData[]? data)
public override void Handle(PlayerControl playerControl, NetData[] data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
ModdedOptionsManager.HandleSyncOptions(data ?? []);
ModdedOptionsManager.HandleSyncOptions(data);
}
}
}
+8 -13
View File
@@ -6,18 +6,13 @@ using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncRoleOptions)]
internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
public class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[]? data)
public override void Write(MessageWriter writer, NetData[] data)
{
if (data == null)
{
writer.WritePacked(0U);
return;
}
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
{
@@ -32,21 +27,21 @@ internal sealed class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : Player
var data = new NetData[length];
for (var i = 0; i < length; i++)
{
var dataId = reader.ReadPackedUInt32();
var id = reader.ReadPackedUInt32();
var bytes = reader.ReadBytesAndSize();
data[i] = new NetData(dataId, bytes);
data[i] = new NetData(id, bytes);
}
return data;
}
public override void Handle(PlayerControl playerControl, NetData[]? data)
public override void Handle(PlayerControl playerControl, NetData[] data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
CustomRoleManager.HandleSyncRoleOptions(data ?? []);
CustomRoleManager.HandleSyncRoleOptions(data);
}
}
}
+4 -24
View File
@@ -1,31 +1,20 @@
using HarmonyLib;
using InnerNet;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Player;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
namespace MiraAPI.Patches;
/// <summary>
/// Sync all options, role settings, and modifiers to the player when they join the game.
/// </summary>
[HarmonyPatch(typeof(AmongUsClient))]
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))]
public static class AmongUsClientSyncPatch
{
[HarmonyPostfix]
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.CreatePlayer))]
public static void CreatePlayerPatch(ClientData clientData)
public static void Postfix(ClientData clientData)
{
var joinEvent = new PlayerJoinEvent(clientData);
MiraEventManager.InvokeEvent(joinEvent);
if (!AmongUsClient.Instance.AmHost)
{
return;
}
if (clientData.Id == AmongUsClient.Instance.HostId)
{
return;
@@ -33,14 +22,5 @@ public static class AmongUsClientSyncPatch
ModdedOptionsManager.SyncAllOptions(clientData.Id);
CustomRoleManager.SyncAllRoleSettings(clientData.Id);
ModifierManager.SyncAllModifiers(clientData.Id);
}
[HarmonyPostfix]
[HarmonyPatch(typeof(AmongUsClient), nameof(AmongUsClient.OnPlayerLeft))]
public static void PlayerLeftPatch(ClientData data, DisconnectReasons reason)
{
var leftEvent = new PlayerLeaveEvent(data, reason);
MiraEventManager.InvokeEvent(leftEvent);
}
}
}
@@ -1,58 +0,0 @@
using HarmonyLib;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Patches.Colors;
[HarmonyPatch(typeof(PlayerTab))]
public static class ScrollingColorsPatch
{
// Collider
private static BoxCollider2D? _collider;
/// <summary>
/// Add scrolling to the colors tab
/// </summary>
[HarmonyPostfix, HarmonyPatch("OnEnable")]
public static void AddScrollingToColorsTabPatch(PlayerTab __instance)
{
if (!PlayerCustomizationMenu.Instance)
{
return;
}
var tab = PlayerCustomizationMenu.Instance.Tabs[1].Tab;
if (__instance.scroller == null)
{
var newScroller = Object.Instantiate(tab.scroller, __instance.transform, true);
newScroller.Inner.transform.DestroyChildren();
var maskObj = new GameObject
{
layer = 5,
name = "SpriteMask",
};
maskObj.transform.SetParent(__instance.transform);
maskObj.transform.localPosition = new Vector3(0, 0, 0);
maskObj.transform.localScale = new Vector3(500, 4.76f);
var mask = maskObj.AddComponent<SpriteMask>();
mask.sprite = MiraAssets.Empty.LoadAsset();
_collider = maskObj.AddComponent<BoxCollider2D>();
_collider.size = new Vector2(1f, 0.75f);
_collider.enabled = true;
__instance.scroller = newScroller;
}
foreach (var chip in __instance.ColorChips)
{
chip.transform.SetParent(__instance.scroller.Inner);
chip.Button.ClickMask = _collider;
chip.GetComponent<SpriteRenderer>().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
__instance.SetScrollerBounds();
}
}
-42
View File
@@ -1,42 +0,0 @@
using HarmonyLib;
using MiraAPI.Events;
using MiraAPI.Events.Vanilla.Usables;
using System.Collections.Generic;
using System.Reflection;
namespace MiraAPI.Patches.Events;
[HarmonyPatch]
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(DoorConsole), nameof(DoorConsole.CanUse));
yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse));
yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.CanUse));
}
[HarmonyPriority(Priority.Last)]
[HarmonyPrefix]
public static bool CanUsePatch(Il2CppSystem.Object __instance, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] out bool canUse, [HarmonyArgument(2)] out bool couldUse)
{
canUse = couldUse = false;
IUsable usable = __instance.Cast<IUsable>();
if (usable != null)
{
var @event = new PlayerCanUseEvent(__instance.Cast<IUsable>());
MiraEventManager.InvokeEvent(@event);
return !@event.IsCancelled;
}
return true;
}
}
Loaded 100 of 176 files, more files were not shown because too many files have changed in this diff. Show more