mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-22 07:35:55 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bdb8852347 | ||
|
|
4775bbfeaa | ||
|
|
0aa164de99 | ||
|
|
5ec91813b7 | ||
|
|
e2cabc6782 | ||
|
|
126df24a23 | ||
|
|
8178967fd8 | ||
|
|
0e40526d26 | ||
|
|
7bb9733513 | ||
|
|
1c97a4d32a | ||
|
|
1f357b5e08 | ||
|
|
264defe8bf | ||
|
|
c318c447f7 | ||
|
|
06c59c5103 | ||
|
|
8fe58ca6ca | ||
|
|
1d92404b0e | ||
|
|
0429cd8005 | ||
|
|
dc5bb4177f | ||
|
|
5a95a6b352 | ||
|
|
7999506359 | ||
|
|
0b36939858 | ||
|
|
09ce9f7cfb | ||
|
|
aba4b30027 | ||
|
|
409113e37a | ||
|
|
a52f46972e | ||
|
|
9f975c8263 | ||
|
|
e083c0c420 | ||
|
|
592e636903 | ||
|
|
b139fbe51d | ||
|
|
ce87fc4705 | ||
|
|
f23d21d896 | ||
|
|
84651b90ea | ||
|
|
1428c629b8 | ||
|
|
feb73d6b07 | ||
|
|
54717c495f | ||
|
|
febfda7d6f | ||
|
|
913377e4df | ||
|
|
86893a1e9e | ||
|
|
56c2d9c0df | ||
|
|
ab5f997fb6 | ||
|
|
869d4970fd | ||
|
|
2cbbcca161 | ||
|
|
2f09baa371 | ||
|
|
acf1d44b52 | ||
|
|
03ef684691 | ||
|
|
eafc740aca | ||
|
|
7af85d9fbb | ||
|
|
bdce8fa37d | ||
|
|
9fc5044085 | ||
|
|
7cb103f318 | ||
|
|
dd8d3df2b0 |
No files matched your search
@@ -1,19 +1,28 @@
|
||||
name: CI
|
||||
|
||||
on: workflow_dispatch
|
||||
on: ["workflow_dispatch", "push", "pull_request"]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-20.04
|
||||
runs-on: ubuntu-22.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
submodules: true
|
||||
|
||||
- uses: actions/cache@v3
|
||||
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@v3
|
||||
with:
|
||||
dotnet-version: 6.x
|
||||
dotnet-version: 8.0.x
|
||||
|
||||
- name: Run the Cake script
|
||||
uses: cake-build/cake-action@v2
|
||||
@@ -24,3 +33,19 @@ jobs:
|
||||
with:
|
||||
name: MiraAPI.dll
|
||||
path: MiraAPI/bin/Release/net6.0/MiraAPI.dll
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: AllOfUs.MiraAPI.nupkg
|
||||
path: MiraAPI/bin/Release/AllOfUs.MiraAPI.*.nupkg
|
||||
|
||||
- uses: softprops/action-gh-release@v1
|
||||
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 }}
|
||||
+2
-1
@@ -4,4 +4,5 @@
|
||||
obj/
|
||||
bin/
|
||||
|
||||
*.user
|
||||
*.user
|
||||
*.lock.json
|
||||
@@ -0,0 +1,14 @@
|
||||
<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.8.13" 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>
|
||||
@@ -0,0 +1,45 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DebugType>embedded</DebugType>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.1.4</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>
|
||||
@@ -18,12 +18,12 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
Target.AddModifier<FreezeModifier>();
|
||||
Target?.RpcAddModifier<FreezeModifier>();
|
||||
}
|
||||
|
||||
public override PlayerControl GetTarget()
|
||||
public override PlayerControl? GetTarget()
|
||||
{
|
||||
return PlayerControl.LocalPlayer.Data.Role.FindClosestTarget();
|
||||
return PlayerControl.LocalPlayer.GetClosestPlayer(true, Distance, false);
|
||||
}
|
||||
|
||||
public override void SetOutline(bool active)
|
||||
@@ -31,14 +31,13 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
||||
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue));
|
||||
}
|
||||
|
||||
public override bool IsTargetValid(PlayerControl target)
|
||||
public override bool IsTargetValid(PlayerControl? target)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override bool Enabled(RoleBehaviour role)
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
return role is FreezerRole;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
using MiraAPI.Example;
|
||||
using MiraAPI.Example.Modifiers;
|
||||
using MiraAPI.Example.Modifiers;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
|
||||
[RegisterButton]
|
||||
public class MeetingButton : CustomActionButton
|
||||
{
|
||||
@@ -18,7 +19,7 @@ public class MeetingButton : CustomActionButton
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
|
||||
public override bool Enabled(RoleBehaviour role)
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
return PlayerControl.LocalPlayer.HasModifier<CaptainModifier>();
|
||||
}
|
||||
@@ -41,7 +42,7 @@ public class MeetingButton : CustomActionButton
|
||||
|
||||
if (UsesLeft == 0 && PlayerControl.LocalPlayer.HasModifier<CaptainModifier>())
|
||||
{
|
||||
PlayerControl.LocalPlayer.RemoveModifier<CaptainModifier>();
|
||||
PlayerControl.LocalPlayer.RpcRemoveModifier<CaptainModifier>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public class TeleportButton : CustomActionButton
|
||||
|
||||
public override int MaxUses => 0;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
||||
public static bool IsZoom { get; private set; }
|
||||
|
||||
public override bool Enabled(RoleBehaviour role)
|
||||
|
||||
@@ -5,5 +5,8 @@ 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");
|
||||
}
|
||||
@@ -22,6 +22,5 @@ public partial class ExamplePlugin : BasePlugin, IMiraPlugin
|
||||
public override void Load()
|
||||
{
|
||||
Harmony.PatchAll();
|
||||
AddComponent<MiraDebugWindow>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DebugType>embedded</DebugType>
|
||||
|
||||
<VersionPrefix>1.0.0</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
<Version>1.0.0</Version>
|
||||
<Description>Example mod for Mira API</Description>
|
||||
<Authors>Angxl, XtraCube</Authors>
|
||||
</PropertyGroup>
|
||||
|
||||
<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.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" />
|
||||
@@ -28,9 +12,8 @@
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\ExampleButton.png" />
|
||||
<EmbeddedResource Include="Resources\FortniteBanner.jpeg" />
|
||||
<EmbeddedResource Include="Resources\TeleportButton.png" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" UseSymboliclinksIfPossible="true" />
|
||||
</Target>
|
||||
<Import Project="..\AmongUs.props" />
|
||||
</Project>
|
||||
@@ -1,6 +1,4 @@
|
||||
using MiraAPI.Example.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.ImGui;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
@@ -21,11 +19,11 @@ public class MiraDebugWindow(IntPtr ptr) : MonoBehaviour(ptr)
|
||||
}
|
||||
})
|
||||
{
|
||||
Enabled = true
|
||||
Enabled = true,
|
||||
};
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
DebuggingWindow.OnGUI();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace MiraAPI.Example.Modifiers;
|
||||
public class CaptainModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "Captain";
|
||||
|
||||
public override int GetAmountPerGame()
|
||||
{
|
||||
return 1;
|
||||
@@ -16,4 +17,4 @@ public class CaptainModifier : GameModifier
|
||||
{
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
using MiraAPI.Modifiers;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Modifiers.Freezer;
|
||||
|
||||
@@ -14,15 +17,25 @@ public class FreezeModifier : TimedModifier
|
||||
|
||||
public override void OnActivate()
|
||||
{
|
||||
if (Player.AmOwner)
|
||||
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)
|
||||
if (Player?.AmOwner == true)
|
||||
{
|
||||
Player.moveable = true;
|
||||
}
|
||||
|
||||
@@ -19,16 +19,13 @@ public class ExampleOptions : AbstractOptionGroup
|
||||
[ModdedNumberOption("Number Opt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Percent)]
|
||||
public float NumberOpt { get; set; } = 4f;
|
||||
|
||||
[ModdedStringOption("String Opt", ["Hello", "Hey", "Bye"])]
|
||||
public int StringOpt { get; set; } = 2;
|
||||
|
||||
[ModdedEnumOption("Enum Opt", typeof(TestEnum))]
|
||||
public TestEnum EnumOpt { get; set; } = TestEnum.Mira;
|
||||
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public BestApi Opt { get; set; } = BestApi.MiraAPI;
|
||||
}
|
||||
|
||||
public enum TestEnum
|
||||
public enum BestApi
|
||||
{
|
||||
Welcome,
|
||||
To,
|
||||
Mira
|
||||
}
|
||||
MiraAPI,
|
||||
Mitochondria,
|
||||
Reactor,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Example.Options;
|
||||
|
||||
@@ -11,17 +12,18 @@ public class ExampleOptions2 : AbstractOptionGroup
|
||||
|
||||
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
|
||||
{
|
||||
Visible = () => OptionGroupSingleton<ExampleOptions2>.Instance.ToggleOpt1.Value
|
||||
Visible = () => OptionGroupSingleton<ExampleOptions2>.Instance.ToggleOpt1.Value,
|
||||
};
|
||||
|
||||
public ModdedStringOption StringOpt { get; } = new("String Opt", 0, ["Choice 1", "Choice 2", "Choice 3"]);
|
||||
|
||||
public ModdedEnumOption EnumOpt { get; } = new("Enum Opt", 0, typeof(TestingEnum));
|
||||
public ModdedEnumOption EnumOpt { get; } = new("Enum Opt", 0, typeof(TestingData))
|
||||
{
|
||||
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
|
||||
};
|
||||
}
|
||||
|
||||
public enum TestingEnum
|
||||
public enum TestingData
|
||||
{
|
||||
Happy,
|
||||
Sad,
|
||||
Neutral
|
||||
}
|
||||
Neutral,
|
||||
}
|
||||
@@ -20,4 +20,4 @@ public class TeleporterOptions : AbstractOptionGroup
|
||||
|
||||
[ModdedNumberOption("Zoom Distance", 4, 15)]
|
||||
public float ZoomDistance { get; set; } = 6;
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
@@ -1,4 +1,5 @@
|
||||
using MiraAPI.Roles;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -12,6 +13,7 @@ public class FreezerRole : ImpostorRole, ICustomRole
|
||||
public string RoleDescription => RoleLongDescription;
|
||||
public Color RoleColor => Palette.Blue;
|
||||
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
|
||||
[HideFromIl2Cpp]
|
||||
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
|
||||
|
||||
}
|
||||
public int MaxPlayers => 2;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MiraAPI.Roles;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -12,6 +13,6 @@ public class TeleporterRole : CrewmateRole, ICustomRole
|
||||
public string RoleDescription => RoleLongDescription;
|
||||
public Color RoleColor => new Color32(221, 176, 152, 255);
|
||||
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
|
||||
[HideFromIl2Cpp]
|
||||
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,42 @@ 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,18 +4,29 @@ using HarmonyLib;
|
||||
|
||||
namespace MiraAPI.Colors;
|
||||
|
||||
internal static class PaletteManager
|
||||
/// <summary>
|
||||
/// Used to register and track custom colors.
|
||||
/// </summary>
|
||||
public static class PaletteManager
|
||||
{
|
||||
public static readonly List<CustomColor> CustomColors = [];
|
||||
|
||||
public static void RegisterAllColors()
|
||||
{
|
||||
var colors = CustomColors.Select(x => x.MainColor);
|
||||
var shadowColors = CustomColors.Select(x => x.ShadowColor);
|
||||
var stringNames = CustomColors.Select(x => x.Name);
|
||||
/// <summary>
|
||||
/// Gets all registered custom colors.
|
||||
/// </summary>
|
||||
public static CustomColor[] RegisteredColors => [.. CustomColors];
|
||||
|
||||
Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors.ToArray());
|
||||
Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors.ToArray());
|
||||
Palette.ColorNames = Palette.ColorNames.ToArray().AddRangeToArray(stringNames.ToArray());
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -2,5 +2,8 @@
|
||||
|
||||
namespace MiraAPI.Colors;
|
||||
|
||||
/// <summary>
|
||||
/// Used to mark a class for custom colors registration.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterCustomColorsAttribute : Attribute;
|
||||
public class RegisterCustomColorsAttribute : Attribute;
|
||||
@@ -3,74 +3,82 @@
|
||||
namespace MiraAPI.GameModes;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for custom gamemodes
|
||||
/// Base class for custom gamemodes.
|
||||
/// </summary>
|
||||
public abstract class CustomGameMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Game mode name
|
||||
/// Gets the game mode name.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Game mode description
|
||||
/// Gets the game mode description.
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Game mode ID
|
||||
/// Gets the 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 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 the 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;
|
||||
@@ -78,61 +86,69 @@ 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>
|
||||
public virtual bool CanAccessRolesTab() => true;
|
||||
/// <returns>True if Role Settings are enabled in this game mode.</returns>
|
||||
public virtual bool AreRoleSettingsEnabled() => true;
|
||||
|
||||
/// <summary>
|
||||
/// Should Game Settings be available when this gamemode is selected?
|
||||
/// Should Game Settings be available when this gamemode is selected.
|
||||
/// </summary>
|
||||
public virtual bool CanAccessSettingsTab() => true;
|
||||
/// <returns>True if Game Settings are enabled in this mode.</returns>
|
||||
public virtual bool AreGameSettingsEnabled() => true;
|
||||
|
||||
/// <summary>
|
||||
/// Custom winner selection
|
||||
/// Custom winner selection.
|
||||
/// </summary>
|
||||
/// <returns>List of winners</returns>
|
||||
public virtual List<NetworkedPlayerInfo> CalculateWinners() => null;
|
||||
/// <returns>List of winners or null.</returns>
|
||||
public virtual List<NetworkedPlayerInfo>? CalculateWinners() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Show gamemode in Intro Cutscene
|
||||
/// Show gamemode in Intro Cutscene.
|
||||
/// </summary>
|
||||
public virtual bool ShowCustomRoleScreen() => false;
|
||||
/// <returns>True if the game mode should be shown in the intro cutscene.</returns>
|
||||
public virtual bool ShowGameModeIntroCutscene() => false;
|
||||
|
||||
/// <summary>
|
||||
/// Can Admin be used in this gamemode
|
||||
/// Can Admin be used in this gamemode.
|
||||
/// </summary>
|
||||
/// <param name="console">Admin Console</param>
|
||||
/// <param name="console">Admin Console.</param>
|
||||
/// <returns>True if Admin console is enabled.</returns>
|
||||
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>
|
||||
/// <param name="body">Target body for reporting.</param>
|
||||
/// <returns>True if dead bodies can be reported.</returns>
|
||||
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>
|
||||
/// <param name="console">System Console.</param>
|
||||
/// <returns>True if system consoles are enabled in this mode.</returns>
|
||||
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>
|
||||
public virtual bool CanUseConsole(Console console) => true;
|
||||
/// <param name="console">Task console.</param>
|
||||
/// <returns>True if tasks are enabled in this mode.</returns>
|
||||
public virtual bool CanUseTasks(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>
|
||||
/// <param name="map">MapBehaviour object.</param>
|
||||
/// <returns>True if the sabotage map should be shown.</returns>
|
||||
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>
|
||||
/// <param name="vent">Target vent.</param>
|
||||
/// <param name="playerInfo">Player attempting to vent.</param>
|
||||
/// <returns>True if venting is enabled in this mode.</returns>
|
||||
public virtual bool CanVent(Vent vent, NetworkedPlayerInfo playerInfo) => true;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
@@ -4,31 +4,34 @@ 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
|
||||
{
|
||||
public List<IModdedOption> Options { get; } = [];
|
||||
internal List<IModdedOption> Options { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// The name of the group. Visible in options menu.
|
||||
/// Gets the name of the group. Visible in options menu.
|
||||
/// </summary>
|
||||
public abstract string GroupName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A function that returns whether the group should be visible or not.
|
||||
/// Gets the function that determines whether the group should be visible or not.
|
||||
/// </summary>
|
||||
public virtual Func<bool> GroupVisible => () => true;
|
||||
|
||||
/// <summary>
|
||||
/// The group color. This is used to color the group in the options menu.
|
||||
/// Gets the group color. This is used to color the group in the options menu.
|
||||
/// </summary>
|
||||
public virtual Color GroupColor => Color.clear;
|
||||
|
||||
/// <summary>
|
||||
/// The role the group is associated with. This is used for the advanced role options menu.
|
||||
/// Gets the role the group is associated with. This is used for the advanced role options menu.
|
||||
/// </summary>
|
||||
public virtual Type AdvancedRole => null;
|
||||
public virtual Type? AdvancedRole => null;
|
||||
|
||||
internal bool AllOptionsHidden { get; set; }
|
||||
|
||||
internal CategoryHeaderMasked Header { get; set; }
|
||||
}
|
||||
internal CategoryHeaderMasked? Header { get; set; }
|
||||
}
|
||||
@@ -4,24 +4,33 @@ using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
public class ModdedEnumOptionAttribute(string title, Type enumType, Type roleType = null)
|
||||
/// <summary>
|
||||
/// Attribute for creating an enum option.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? values = null, Type? roleType = null)
|
||||
: ModdedOptionAttribute(title, roleType)
|
||||
{
|
||||
internal override IModdedOption CreateOption(object value, PropertyInfo property)
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
var opt = new ModdedEnumOption(Title, (int)value, enumType, RoleType);
|
||||
var opt = new ModdedEnumOption(Title, (int)(value ?? 0), enumType, values, RoleType);
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var opt = HolderOption as ModdedEnumOption;
|
||||
opt.SetValue((int)value);
|
||||
opt?.SetValue((int)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
var opt = HolderOption as ModdedEnumOption;
|
||||
return Enum.ToObject(enumType, opt.Value);
|
||||
if (HolderOption is ModdedEnumOption opt)
|
||||
{
|
||||
return Enum.ToObject(enumType, opt.Value);
|
||||
}
|
||||
throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,10 @@ 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,
|
||||
@@ -12,24 +16,28 @@ public class ModdedNumberOptionAttribute(
|
||||
float increment = 1,
|
||||
MiraNumberSuffixes suffixType = MiraNumberSuffixes.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)
|
||||
{
|
||||
var toggleOpt = new ModdedNumberOption(Title, (float)value, min, max, increment, suffixType, zeroInfinity, RoleType);
|
||||
return toggleOpt;
|
||||
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, zeroInfinity, RoleType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var toggleOpt = HolderOption as ModdedNumberOption;
|
||||
toggleOpt.SetValue((float)value);
|
||||
var opt = HolderOption as ModdedNumberOption;
|
||||
opt?.SetValue((float)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
var toggleOpt = HolderOption as ModdedNumberOption;
|
||||
return toggleOpt.Value;
|
||||
if (HolderOption is ModdedNumberOption opt)
|
||||
{
|
||||
return opt.Value;
|
||||
}
|
||||
throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedNumberOption");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,37 @@ 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
|
||||
{
|
||||
public IModdedOption HolderOption { get; internal set; }
|
||||
|
||||
public string Title { get; private set; } = title;
|
||||
|
||||
protected Type RoleType { get; private set; } = roleType;
|
||||
internal IModdedOption? HolderOption { get; set; }
|
||||
|
||||
public abstract void SetValue(object val);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the title of the option.
|
||||
/// </summary>
|
||||
public string Title { get; private set; } = title;
|
||||
|
||||
/// <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 object GetValue();
|
||||
|
||||
internal abstract IModdedOption CreateOption(object value, PropertyInfo property);
|
||||
}
|
||||
internal abstract IModdedOption? CreateOption(object? value, PropertyInfo property);
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
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)
|
||||
{
|
||||
var opt = HolderOption as ModdedStringOption;
|
||||
opt.SetValue((int)value);
|
||||
}
|
||||
|
||||
public override object GetValue()
|
||||
{
|
||||
var opt = HolderOption as ModdedStringOption;
|
||||
return opt.Value;
|
||||
}
|
||||
}
|
||||
@@ -4,23 +4,34 @@ using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameOptions.Attributes;
|
||||
|
||||
public class ModdedToggleOptionAttribute(string title, Type roleType = null) : ModdedOptionAttribute(title, roleType)
|
||||
/// <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)
|
||||
{
|
||||
internal override IModdedOption CreateOption(object value, PropertyInfo property)
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
var toggleOpt = new ModdedToggleOption(Title, (bool)value, RoleType);
|
||||
var toggleOpt = new ModdedToggleOption(Title, (bool)(value ?? false), RoleType);
|
||||
return toggleOpt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var toggleOpt = HolderOption as ModdedToggleOption;
|
||||
toggleOpt.SetValue((bool)value);
|
||||
var opt = HolderOption as ModdedToggleOption;
|
||||
opt?.SetValue((bool)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
var toggleOpt = HolderOption as ModdedToggleOption;
|
||||
return toggleOpt.Value;
|
||||
if (HolderOption is ModdedToggleOption opt)
|
||||
{
|
||||
return opt.Value;
|
||||
}
|
||||
throw new InvalidOperationException($"Holder option for {Title} is not a ModdedToggleOption.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,25 +1,24 @@
|
||||
using MiraAPI.PluginLoading;
|
||||
using System;
|
||||
using System;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
public interface IModdedOption
|
||||
internal interface IModdedOption
|
||||
{
|
||||
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 abstract float GetFloatData();
|
||||
public NetData GetNetData();
|
||||
public void HandleNetData(byte[] data);
|
||||
}
|
||||
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);
|
||||
}
|
||||
@@ -1,17 +1,21 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
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
|
||||
{
|
||||
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
|
||||
@@ -19,10 +23,10 @@ public static class ModdedOptionsManager
|
||||
|
||||
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = new();
|
||||
internal static readonly List<AbstractOptionGroup> Groups = [];
|
||||
|
||||
|
||||
internal 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)
|
||||
@@ -40,10 +44,10 @@ public static class ModdedOptionsManager
|
||||
Groups.Add(group);
|
||||
TypeToGroup.Add(type, group);
|
||||
pluginInfo.OptionGroups.Add(group);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
internal static void RegisterPropertyOption(Type type, PropertyInfo property, MiraPluginInfo pluginInfo)
|
||||
{
|
||||
if (!TypeToGroup.TryGetValue(type, out var group))
|
||||
@@ -57,11 +61,15 @@ public static class ModdedOptionsManager
|
||||
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
RegisterOption(option, group, pluginInfo);
|
||||
|
||||
RegisterOption(option, group, property.Name, 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))
|
||||
{
|
||||
@@ -76,13 +84,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));
|
||||
@@ -93,25 +101,33 @@ public static class ModdedOptionsManager
|
||||
|
||||
OptionAttributes.Add(property, attribute);
|
||||
attribute.HolderOption = option;
|
||||
|
||||
RegisterOption(option, group, pluginInfo);
|
||||
|
||||
RegisterOption(option, group, property.Name, pluginInfo);
|
||||
}
|
||||
|
||||
private static void RegisterOption(IModdedOption option, AbstractOptionGroup group, MiraPluginInfo pluginInfo)
|
||||
private static void RegisterOption(
|
||||
IModdedOption option,
|
||||
AbstractOptionGroup group,
|
||||
string propertyName,
|
||||
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)
|
||||
{
|
||||
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, chunks.Dequeue());
|
||||
@@ -120,40 +136,59 @@ public static class ModdedOptionsManager
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
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().Equals(__originalMethod)).Value;
|
||||
|
||||
attribute?.SetValue(value);
|
||||
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod() == __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().Equals(__originalMethod)).Value;
|
||||
|
||||
if (attribute == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod() == __originalMethod).Value;
|
||||
__result = attribute.GetValue();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,21 +3,17 @@ using System.Linq;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton for option groups.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The option group type.</typeparam>
|
||||
public class OptionGroupSingleton<T> where T : AbstractOptionGroup
|
||||
{
|
||||
private static T _instance;
|
||||
private static T? _instance;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the instance of the option group.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">Can't</exception>
|
||||
public static T Instance => _instance ??= ModdedOptionsManager.Groups.OfType<T>().Single();
|
||||
}
|
||||
@@ -1,38 +1,57 @@
|
||||
using Reactor.Localization.Utilities;
|
||||
using MiraAPI.Networking;
|
||||
using Reactor.Localization.Utilities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using MiraAPI.Networking;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// An option for selecting an enum value.
|
||||
/// </summary>
|
||||
public class ModdedEnumOption : ModdedOption<int>
|
||||
{
|
||||
public string[] Values { get; }
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
|
||||
public ModdedEnumOption(string title, int defaultValue, Type enumType, Type roleType=null) : base(title, defaultValue, roleType)
|
||||
/// <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)
|
||||
{
|
||||
Values = Enum.GetNames(enumType);
|
||||
Values = values ?? Enum.GetNames(enumType);
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
var data = (StringGameSetting)Data;
|
||||
|
||||
|
||||
data.Title = StringName;
|
||||
data.Type = global::OptionTypes.String;
|
||||
data.Values = Values.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
data.Values = values is null ?
|
||||
Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray()
|
||||
: 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 = ((StringGameSetting)Data).Values;
|
||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||
stringOption.Value = Value;
|
||||
|
||||
OptionBehaviour = stringOption;
|
||||
@@ -40,43 +59,42 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
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));
|
||||
}
|
||||
|
||||
public override string GetHudStringText()
|
||||
{
|
||||
return Title + ": " + Values[Value];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return optionBehaviour.GetInt();
|
||||
}
|
||||
|
||||
public override void OnValueChanged(int newValue)
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
|
||||
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data?.GetValueString(newValue), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var opt = OptionBehaviour as StringOption;
|
||||
if (opt)
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,19 +7,56 @@ 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 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 bool ZeroInfinity { get; }
|
||||
|
||||
public ModdedNumberOption(string title, float defaultValue, float min, float max, float increment, MiraNumberSuffixes suffixType, bool zeroInfinity = false, Type roleType = null) : base(title, defaultValue, roleType)
|
||||
/// <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)
|
||||
{
|
||||
Min = min;
|
||||
Max = max;
|
||||
@@ -37,13 +74,18 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
data.Value = Value;
|
||||
data.Increment = Increment;
|
||||
data.ValidRange = new FloatRange(Min, Max);
|
||||
data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0";
|
||||
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.ZeroIsInfinity = ZeroInfinity;
|
||||
data.SuffixType = (NumberSuffixes)SuffixType;
|
||||
data.OptionName = FloatOptionNames.Invalid;
|
||||
}
|
||||
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
Transform container)
|
||||
{
|
||||
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
|
||||
|
||||
@@ -54,56 +96,52 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
numberOption.Value = Value;
|
||||
numberOption.Increment = Increment;
|
||||
numberOption.ValidRange = new FloatRange(Min, Max);
|
||||
numberOption.FormatString = ((FloatGameSetting)Data).FormatString;
|
||||
numberOption.FormatString = (Data as FloatGameSetting)?.FormatString ?? "0.0";
|
||||
numberOption.ZeroIsInfinity = ZeroInfinity;
|
||||
numberOption.SuffixType = (NumberSuffixes)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));
|
||||
}
|
||||
|
||||
public override string GetHudStringText()
|
||||
{
|
||||
return Title + ": " + Value + Helpers.GetSuffix(SuffixType);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return Mathf.Clamp(optionBehaviour.GetFloat(), Min, Max);
|
||||
}
|
||||
|
||||
public override void OnValueChanged(float newValue)
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(float newValue)
|
||||
{
|
||||
Value = Mathf.Clamp(newValue, Min, Max);
|
||||
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Value), false);
|
||||
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(
|
||||
StringName,
|
||||
Data?.GetValueString(Value),
|
||||
false);
|
||||
|
||||
if (!OptionBehaviour)
|
||||
if (OptionBehaviour is NumberOption opt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var opt = OptionBehaviour as NumberOption;
|
||||
if (opt)
|
||||
{
|
||||
opt.Value = newValue;
|
||||
opt.Value = Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,33 +9,101 @@ 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;
|
||||
private IMiraPlugin? _parentMod;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier of the option.
|
||||
/// </summary>
|
||||
public uint Id { get; }
|
||||
public BaseGameSetting Data { get; protected init; }
|
||||
public IMiraPlugin ParentMod
|
||||
|
||||
/// <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
|
||||
{
|
||||
get => _parentMod;
|
||||
set
|
||||
{
|
||||
if (_parentMod != null || value == null) return;
|
||||
_parentMod = value;
|
||||
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
|
||||
Value = entry.Value;
|
||||
}
|
||||
}
|
||||
public T Value { get; protected set; }
|
||||
public T DefaultValue { get; }
|
||||
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);
|
||||
|
||||
protected ModdedOption(string title, T defaultValue, Type roleType)
|
||||
/// <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 Func<bool> Visible { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the advanced role of the option.
|
||||
/// </summary>
|
||||
public Type? AdvancedRole { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the option behaviour of the option.
|
||||
/// </summary>
|
||||
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)
|
||||
{
|
||||
Id = ModdedOptionsManager.NextId;
|
||||
Title = title;
|
||||
@@ -50,44 +118,80 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
}
|
||||
}
|
||||
|
||||
public void ValueChanged(OptionBehaviour optionBehaviour)
|
||||
internal 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) && ChangedEvent != null)
|
||||
|
||||
if (Value?.Equals(oldVal) == false)
|
||||
{
|
||||
ChangedEvent.Invoke(Value);
|
||||
ChangedEvent?.Invoke(Value);
|
||||
}
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
|
||||
}
|
||||
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
|
||||
{
|
||||
entry.Value = Value;
|
||||
}
|
||||
|
||||
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
|
||||
{
|
||||
entry.Value = Value;
|
||||
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
public abstract void OnValueChanged(T newValue);
|
||||
/// <summary>
|
||||
/// Handles the value changed event.
|
||||
/// </summary>
|
||||
/// <param name="newValue">The new value.</param>
|
||||
protected 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);
|
||||
|
||||
public abstract OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
|
||||
|
||||
public abstract string GetHudStringText();
|
||||
}
|
||||
/// <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);
|
||||
}
|
||||
@@ -1,82 +0,0 @@
|
||||
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 float GetFloatData()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
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,9 +5,18 @@ using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// A modded toggle option.
|
||||
/// </summary>
|
||||
public class ModdedToggleOption : ModdedOption<bool>
|
||||
{
|
||||
public ModdedToggleOption(string title, bool defaultValue, Type roleType = null) : base(title, defaultValue, roleType)
|
||||
/// <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)
|
||||
{
|
||||
Data = ScriptableObject.CreateInstance<CheckboxGameSetting>();
|
||||
|
||||
@@ -16,6 +25,7 @@ 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);
|
||||
@@ -32,32 +42,32 @@ 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));
|
||||
}
|
||||
|
||||
public override string GetHudStringText()
|
||||
{
|
||||
return Title + ": " + (Value ? "On" : "Off");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return optionBehaviour.GetBool();
|
||||
}
|
||||
|
||||
public override void OnValueChanged(bool newValue)
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(bool newValue)
|
||||
{
|
||||
DestroyableSingleton<HudManager>.Instance.Notifier.AddSettingsChangeMessage(StringName, newValue ? "On" : "Off", false);
|
||||
if (!OptionBehaviour)
|
||||
@@ -65,10 +75,9 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
return;
|
||||
}
|
||||
|
||||
var toggleOpt = OptionBehaviour as ToggleOption;
|
||||
if (toggleOpt)
|
||||
if (OptionBehaviour is ToggleOption toggleOpt)
|
||||
{
|
||||
toggleOpt.CheckMark.enabled = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,17 @@
|
||||
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,
|
||||
BottomRight
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Placed in the bottom right.
|
||||
/// </summary>
|
||||
BottomRight,
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
#nullable enable
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
@@ -8,67 +6,67 @@ using UnityEngine.UI;
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Class for making custom action buttons. More customizable than the default Action/Ability buttons in the base game
|
||||
/// Class for making custom action buttons. More customizable than the default Action/Ability buttons in the base game.
|
||||
/// </summary>
|
||||
public abstract class CustomActionButton
|
||||
{
|
||||
/// <summary>
|
||||
/// The name and text of the button.
|
||||
/// Gets the name and text of the button.
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// How long the button takes to cooldown.
|
||||
/// Gets the button's cooldown duration in seconds.
|
||||
/// </summary>
|
||||
public abstract float Cooldown { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the button has an effect ability, how long the effect should last. To disable effects, set to 0.
|
||||
/// Gets the button's effect duration in seconds. If the button has no effect, 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.
|
||||
/// Gets the maximum amount of uses the button has. If the button has infinite uses, 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.
|
||||
/// Gets the sprite of the button. Use <see cref="LoadableResourceAsset"/> to load a sprite from a resource path. Use <see cref="LoadableBundleAsset{T}"/> to load a sprite from an asset bundle.
|
||||
/// </summary>
|
||||
public abstract LoadableAsset<Sprite> Sprite { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The location of the button on the screen
|
||||
/// Gets the location of the button on the screen.
|
||||
/// </summary>
|
||||
public virtual ButtonLocation Location => ButtonLocation.BottomLeft;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the button has an effect ability.
|
||||
/// Gets a value indicating whether the button has an effect ability.
|
||||
/// </summary>
|
||||
public bool HasEffect => EffectDuration > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the button has limited uses.
|
||||
/// Gets a value indicating whether the button has limited uses.
|
||||
/// </summary>
|
||||
public bool LimitedUses => MaxUses > 0;
|
||||
|
||||
/// <summary>
|
||||
/// Returns true if the effect is currently active.
|
||||
/// Gets or sets a value indicating whether the effect is currently active, if there is one.
|
||||
/// </summary>
|
||||
protected bool EffectActive;
|
||||
public bool EffectActive { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// A timer variable to measure cooldowns and effects.
|
||||
/// Gets or sets the amount of uses left.
|
||||
/// </summary>
|
||||
protected float Timer;
|
||||
public int UsesLeft { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns the amount of uses left.
|
||||
/// Gets or sets the timer variable to measure cooldowns and effects.
|
||||
/// </summary>
|
||||
protected int UsesLeft;
|
||||
public float Timer { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The button object in game. This is created by Mira API automatically.
|
||||
/// Gets the button object in game. This is created by Mira API automatically.
|
||||
/// </summary>
|
||||
protected ActionButton? Button { get; private set; }
|
||||
|
||||
@@ -85,7 +83,7 @@ public abstract class CustomActionButton
|
||||
|
||||
Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent);
|
||||
Button.name = Name + "Button";
|
||||
Button.OverrideText(Name.ToUpper());
|
||||
Button.OverrideText(Name.ToUpperInvariant());
|
||||
|
||||
Button.graphic.sprite = Sprite.LoadAsset();
|
||||
|
||||
@@ -117,7 +115,7 @@ public abstract class CustomActionButton
|
||||
/// <summary>
|
||||
/// A utility function to override the sprite of the button.
|
||||
/// </summary>
|
||||
/// <param name="sprite">The new sprite to override with</param>
|
||||
/// <param name="sprite">The new sprite to override with.</param>
|
||||
public void OverrideSprite(Sprite sprite)
|
||||
{
|
||||
if (Button != null)
|
||||
@@ -129,17 +127,73 @@ public abstract class CustomActionButton
|
||||
/// <summary>
|
||||
/// A utility function to override the name of the button.
|
||||
/// </summary>
|
||||
/// <param name="name">The new name to override with</param>
|
||||
/// <param name="name">The new name to override with.</param>
|
||||
public void OverrideName(string name)
|
||||
{
|
||||
Button?.OverrideText(name);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the button's timer.
|
||||
/// </summary>
|
||||
/// <param name="time">The time you want to set to.</param>
|
||||
public void SetTimer(float time)
|
||||
{
|
||||
Timer = Mathf.Clamp(time, -1, float.MaxValue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Increase the button's timer.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount you want to increase by.</param>
|
||||
public 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 void DecreaseTimer(float amount)
|
||||
{
|
||||
SetTimer(Timer - amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the amount of uses this button has left.
|
||||
/// </summary>
|
||||
/// <param name="amount">The amount you want to set to.</param>
|
||||
public void SetUses(int amount)
|
||||
{
|
||||
UsesLeft = Mathf.Clamp(amount, 0, int.MaxValue);
|
||||
}
|
||||
|
||||
/// <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 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 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) { }
|
||||
/// <param name="playerControl">the local PlayerControl.</param>
|
||||
protected virtual void FixedUpdate(PlayerControl playerControl)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback method for the button click event.
|
||||
@@ -151,13 +205,16 @@ 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);
|
||||
|
||||
/// <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.
|
||||
/// </summary>
|
||||
public virtual void OnEffectEnd() { }
|
||||
public virtual void OnEffectEnd()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// When the button is enabled, this method is called to determine if the button can be used.
|
||||
@@ -175,7 +232,7 @@ 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)
|
||||
{
|
||||
@@ -218,7 +275,7 @@ 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)
|
||||
@@ -254,23 +311,20 @@ public abstract class CustomActionButton
|
||||
public abstract class CustomActionButton<T> : CustomActionButton where T : MonoBehaviour
|
||||
{
|
||||
/// <summary>
|
||||
/// The target object of the button.
|
||||
/// Gets or sets the target object of the button.
|
||||
/// </summary>
|
||||
public T? Target { get; private set; }
|
||||
public T? Target { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// The distance the player must be from the target object to use the button.
|
||||
/// 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>
|
||||
/// An optional collider tag to filter the target object by.
|
||||
/// </summary>
|
||||
public virtual string? ColliderTag => null;
|
||||
|
||||
/// <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;
|
||||
@@ -279,15 +333,16 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
|
||||
/// <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>
|
||||
/// <param name="active">Should the outline be active.</param>
|
||||
public abstract void SetOutline(bool active);
|
||||
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanUse()
|
||||
{
|
||||
var newTarget = GetTarget();
|
||||
@@ -301,4 +356,4 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
|
||||
|
||||
return base.CanUse() && Target;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,10 @@ internal static class CustomButtonManager
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create button from {buttonType.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
CustomButtons.Add(button);
|
||||
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, button);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
@@ -8,26 +6,12 @@ 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 class CustomButtonSingleton<T> where T : CustomActionButton
|
||||
public static class CustomButtonSingleton<T> where T : CustomActionButton
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the instance of the button.
|
||||
/// </summary>
|
||||
public static T Instance => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
|
||||
}
|
||||
+6
-29
@@ -1,15 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<LangVersion>latest</LangVersion>
|
||||
<DebugType>embedded</DebugType>
|
||||
<PackageId>AllOfUs.MiraAPI</PackageId>
|
||||
<Version>0.1.2</Version>
|
||||
<Description>Simple and easy to use Among Us modding API.</Description>
|
||||
<Authors>Angxl, XtraCube</Authors>
|
||||
<Company>All Of Us</Company>
|
||||
<PackageLicenseExpression>LGPL-2.1-or-later</PackageLicenseExpression>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageId>AllOfUs.MiraAPI</PackageId>
|
||||
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
|
||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -17,27 +12,9 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Resources\Checkmark.png" />
|
||||
<EmbeddedResource Include="Resources\CheckMarkBox.png" />
|
||||
<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>
|
||||
<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.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>
|
||||
<EmbeddedResource Include="Resources\**\*.*" />
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
|
||||
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" />
|
||||
</Target>
|
||||
<Import Project="../AmongUs.props" />
|
||||
|
||||
</Project>
|
||||
@@ -2,7 +2,6 @@
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Networking.Attributes;
|
||||
|
||||
@@ -1,14 +1,74 @@
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for all modifiers.
|
||||
/// </summary>
|
||||
public abstract class BaseModifier
|
||||
{
|
||||
public abstract string ModifierName { get; }
|
||||
public virtual bool HideOnUi => false;
|
||||
public PlayerControl Player { get; internal set; }
|
||||
/// <summary>
|
||||
/// Gets the player that the modifier is attached to.
|
||||
/// </summary>
|
||||
public PlayerControl? Player { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier id.
|
||||
/// </summary>
|
||||
public uint ModifierId { get; internal set; }
|
||||
public virtual void OnActivate() { }
|
||||
public virtual void OnDeactivate() { }
|
||||
public virtual void Update() { }
|
||||
public virtual void FixedUpdate() { }
|
||||
public virtual void OnDeath() { }
|
||||
public virtual bool CanVent() => Player.Data.Role.CanVent;
|
||||
}
|
||||
|
||||
/// <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>
|
||||
public virtual void OnDeath()
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the player can vent.
|
||||
/// </summary>
|
||||
/// <returns>True if the player can vent, false otherwise.</returns>
|
||||
public virtual bool CanVent() => Player?.Data.Role.CanVent == true;
|
||||
}
|
||||
@@ -1,33 +1,58 @@
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Modifiers;
|
||||
|
||||
/// <summary>
|
||||
/// The component for handling modifiers.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
|
||||
{
|
||||
public List<BaseModifier> ActiveModifiers { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the active modifiers on the player.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
public ImmutableList<BaseModifier> ActiveModifiers => Modifiers.ToImmutableList();
|
||||
|
||||
public PlayerControl player;
|
||||
[HideFromIl2Cpp]
|
||||
private List<BaseModifier> Modifiers { get; set; } = [];
|
||||
|
||||
private TextMeshPro _modifierText;
|
||||
private PlayerControl? _player;
|
||||
|
||||
public void Start()
|
||||
private TextMeshPro? _modifierText;
|
||||
|
||||
private readonly List<BaseModifier> _toRemove = [];
|
||||
|
||||
private readonly List<BaseModifier> _toAdd = [];
|
||||
|
||||
internal void ClearModifiers()
|
||||
{
|
||||
player = GetComponent<PlayerControl>();
|
||||
ActiveModifiers = [];
|
||||
foreach (var modifier in Modifiers)
|
||||
{
|
||||
modifier.OnDeactivate();
|
||||
}
|
||||
|
||||
if (!player.AmOwner)
|
||||
_toRemove.AddRange(Modifiers);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_player = GetComponent<PlayerControl>();
|
||||
Modifiers = [];
|
||||
|
||||
if (!_player.AmOwner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -36,55 +61,80 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
|
||||
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
|
||||
}
|
||||
|
||||
public void FixedUpdate()
|
||||
private void FixedUpdate()
|
||||
{
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
foreach (var modifier in _toRemove)
|
||||
{
|
||||
modifier.OnDeactivate();
|
||||
Modifiers.Remove(modifier);
|
||||
}
|
||||
|
||||
foreach (var modifier in _toAdd)
|
||||
{
|
||||
Modifiers.Add(modifier);
|
||||
modifier.OnActivate();
|
||||
|
||||
if (_player?.AmOwner == true && modifier is TimedModifier { AutoStart: true } timer)
|
||||
{
|
||||
timer.StartTimer();
|
||||
}
|
||||
}
|
||||
|
||||
if (_toAdd.Count > 0 || _toRemove.Count > 0)
|
||||
{
|
||||
if (_player?.AmOwner == true)
|
||||
{
|
||||
HudManager.Instance?.SetHudActive(true);
|
||||
}
|
||||
_toAdd.Clear();
|
||||
_toRemove.Clear();
|
||||
}
|
||||
|
||||
foreach (var modifier in Modifiers)
|
||||
{
|
||||
modifier.FixedUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
private void Update()
|
||||
{
|
||||
foreach (var modifier in ActiveModifiers)
|
||||
foreach (var modifier in Modifiers)
|
||||
{
|
||||
modifier.Update();
|
||||
}
|
||||
|
||||
var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi);
|
||||
if (!_modifierText || _player?.AmOwner == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var filteredModifiers = Modifiers.Where(mod => !mod.HideOnUi);
|
||||
|
||||
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
|
||||
|
||||
if (player.AmOwner && baseModifiers.Any())
|
||||
|
||||
if (baseModifiers.Length != 0)
|
||||
{
|
||||
var stringBuild = new StringBuilder();
|
||||
foreach (var mod in baseModifiers)
|
||||
{
|
||||
stringBuild.Append($"\n{mod.ModifierName}");
|
||||
if (mod is TimedModifier timer)
|
||||
{
|
||||
stringBuild.Append($" <size=70%>({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)</size>");
|
||||
}
|
||||
stringBuild.Append(CultureInfo.InvariantCulture, $"\n{mod.GetHudString()}");
|
||||
}
|
||||
_modifierText.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
|
||||
_modifierText!.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
|
||||
}
|
||||
else if (player.AmOwner && !baseModifiers.Any() && _modifierText.text != string.Empty)
|
||||
else if (_modifierText!.text != string.Empty)
|
||||
{
|
||||
_modifierText.text = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveModifier(PlayerControl target, uint modifierId)
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// </summary>
|
||||
/// <param name="type">The modifier type.</param>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier(Type type)
|
||||
{
|
||||
if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
var modifierComponent = target.GetModifierComponent();
|
||||
|
||||
var modifier = modifierComponent?.ActiveModifiers.Find(x => x.GetType() == type);
|
||||
var modifier = Modifiers.Find(x => x.GetType() == type);
|
||||
|
||||
if (modifier is null)
|
||||
{
|
||||
@@ -92,58 +142,121 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
|
||||
return;
|
||||
}
|
||||
|
||||
modifier.OnDeactivate();
|
||||
modifierComponent.ActiveModifiers.Remove(modifier);
|
||||
|
||||
if (target.AmOwner)
|
||||
{
|
||||
HudManager.Instance.SetHudActive(true);
|
||||
}
|
||||
RemoveModifier(modifier);
|
||||
}
|
||||
|
||||
public static void AddModifier(PlayerControl target, uint modifierId)
|
||||
/// <summary>
|
||||
/// Removes a modifier from the player.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The modifier type.</typeparam>
|
||||
[HideFromIl2Cpp]
|
||||
public void RemoveModifier<T>() where T : BaseModifier
|
||||
{
|
||||
if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
|
||||
return;
|
||||
}
|
||||
RemoveModifier(typeof(T));
|
||||
}
|
||||
|
||||
if (target.HasModifier(modifierId))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
|
||||
return;
|
||||
}
|
||||
|
||||
var modifier = (BaseModifier)Activator.CreateInstance(type);
|
||||
|
||||
var modifierComponent = target.GetModifierComponent();
|
||||
/// <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 add modifier {type.Name} because it is null.");
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
modifierComponent.ActiveModifiers.Add(modifier);
|
||||
modifier.Player = modifierComponent.player;
|
||||
modifier.ModifierId = modifierId;
|
||||
modifier.OnActivate();
|
||||
|
||||
if (target.AmOwner)
|
||||
{
|
||||
if (modifier is TimedModifier { AutoStart: true } timer)
|
||||
{
|
||||
timer.StartTimer();
|
||||
}
|
||||
|
||||
HudManager.Instance.SetHudActive(true);
|
||||
}
|
||||
RemoveModifier(modifier);
|
||||
}
|
||||
|
||||
[MethodRpc((uint)MiraRpc.RemoveModifier)]
|
||||
public static void RpcRemoveModifier(PlayerControl target, uint modifierId) => RemoveModifier(target, modifierId);
|
||||
/// <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;
|
||||
}
|
||||
|
||||
[MethodRpc((uint)MiraRpc.AddModifier)]
|
||||
public static void RpcAddModifier(PlayerControl target, uint modifierId) => AddModifier(target, modifierId);
|
||||
}
|
||||
_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.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;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,24 @@
|
||||
using MiraAPI.Modifiers.Types;
|
||||
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.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
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
|
||||
{
|
||||
public static readonly Dictionary<uint, Type> IdToTypeModifiers = [];
|
||||
public static readonly Dictionary<Type, uint> TypeToIdModifiers = [];
|
||||
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
|
||||
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
|
||||
|
||||
private static uint _nextId;
|
||||
|
||||
@@ -25,6 +28,26 @@ public static class ModifierManager
|
||||
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 void RegisterModifier(Type modifierType)
|
||||
{
|
||||
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
|
||||
@@ -32,8 +55,8 @@ public static class ModifierManager
|
||||
return;
|
||||
}
|
||||
|
||||
IdToTypeModifiers.Add(GetNextId(), modifierType);
|
||||
TypeToIdModifiers.Add(modifierType, _nextId);
|
||||
IdToTypeModifierMap.Add(GetNextId(), modifierType);
|
||||
TypeToIdModifierMap.Add(modifierType, _nextId);
|
||||
}
|
||||
|
||||
internal static void AssignModifiers(List<PlayerControl> plrs)
|
||||
@@ -42,34 +65,32 @@ public static class ModifierManager
|
||||
|
||||
List<uint> filteredModifiers = [];
|
||||
|
||||
foreach (var modifier in IdToTypeModifiers.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier))))
|
||||
foreach (var modifier in IdToTypeModifierMap.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier))))
|
||||
{
|
||||
var mod = (GameModifier)Activator.CreateInstance(modifier.Value);
|
||||
|
||||
if (mod is null)
|
||||
if (Activator.CreateInstance(modifier.Value) is not GameModifier mod)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create instance of {modifier.Value.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!plrs.Any(x=>IsGameModifierValid(x, mod, modifier.Key)))
|
||||
|
||||
if (!plrs.Exists(x => IsGameModifierValid(x, mod, modifier.Key)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning("No players are valid for modifier: " + mod.ModifierName);
|
||||
continue;
|
||||
}
|
||||
|
||||
var maxCount = plrs.Count(x=>IsGameModifierValid(x, mod, modifier.Key));
|
||||
|
||||
var maxCount = plrs.Count(x => IsGameModifierValid(x, mod, modifier.Key));
|
||||
|
||||
var num = Math.Clamp(mod.GetAmountPerGame(), 0, maxCount);
|
||||
var chance = mod.GetAssignmentChance();
|
||||
|
||||
|
||||
for (var i = 0; i < num; i++)
|
||||
{
|
||||
var randomNum = rand.Next(100);
|
||||
|
||||
if (randomNum < Math.Clamp(chance, 0, 100))
|
||||
{
|
||||
filteredModifiers.Add(TypeToIdModifiers[modifier.Value]);
|
||||
filteredModifiers.Add(TypeToIdModifierMap[modifier.Value]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,34 +100,32 @@ public static class ModifierManager
|
||||
{
|
||||
shuffledModifiers = shuffledModifiers.GetRange(0, plrs.Count);
|
||||
}
|
||||
|
||||
|
||||
while (shuffledModifiers.Count > 0)
|
||||
{
|
||||
var id = shuffledModifiers[0];
|
||||
|
||||
var mod = (GameModifier)Activator.CreateInstance(IdToTypeModifiers[id]);
|
||||
|
||||
if (mod is null)
|
||||
if (Activator.CreateInstance(IdToTypeModifierMap[id]) is not GameModifier mod)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifiers[id].Name}");
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifierMap[id].Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!plrs.Any(x=>IsGameModifierValid(x, mod, id)))
|
||||
|
||||
if (!plrs.Exists(x => IsGameModifierValid(x, mod, id)))
|
||||
{
|
||||
shuffledModifiers.RemoveAt(0);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var plr = plrs.Random();
|
||||
|
||||
if (!IsGameModifierValid(plr, mod, id))
|
||||
if (plr == null || !IsGameModifierValid(plr, mod, id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
shuffledModifiers.RemoveAt(0);
|
||||
ModifierComponent.RpcAddModifier(plr, id);
|
||||
plr.RpcAddModifier(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,17 +135,17 @@ public static class ModifierManager
|
||||
modifier.IsModifierValidOn(player.Data.Role) &&
|
||||
!player.HasModifier(modifierId);
|
||||
}
|
||||
|
||||
|
||||
internal static void SyncAllModifiers(int targetId = -1)
|
||||
{
|
||||
var data = new List<NetData>();
|
||||
|
||||
foreach (var player in GameData.Instance.AllPlayers)
|
||||
foreach (var player in PlayerControl.AllPlayerControls)
|
||||
{
|
||||
data.Add(GetPlayerModifiers(player.Object));
|
||||
data.Add(GetPlayerModifiers(player));
|
||||
}
|
||||
|
||||
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
|
||||
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, [.. data]);
|
||||
}
|
||||
|
||||
internal static void HandleSyncModifiers(NetData[] data)
|
||||
@@ -144,33 +163,40 @@ public static class ModifierManager
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var modifier in modifierComponent.ActiveModifiers)
|
||||
{
|
||||
modifier.OnDeactivate();
|
||||
}
|
||||
modifierComponent.ActiveModifiers.Clear();
|
||||
modifierComponent.ClearModifiers();
|
||||
|
||||
foreach (var id in ids)
|
||||
{
|
||||
ModifierComponent.AddModifier(plr, id);
|
||||
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)
|
||||
private static NetData GetPlayerModifiers(PlayerControl? player)
|
||||
{
|
||||
var bytes = new List<byte>();
|
||||
if (player == null || !player)
|
||||
{
|
||||
return new NetData(0, []);
|
||||
}
|
||||
|
||||
List<byte> bytes = [];
|
||||
var modifierComponent = player.GetComponent<ModifierComponent>();
|
||||
if (!modifierComponent)
|
||||
if (modifierComponent == null || !modifierComponent)
|
||||
{
|
||||
return new NetData(player.PlayerId, []);
|
||||
}
|
||||
|
||||
foreach (var modifier in modifierComponent.ActiveModifiers)
|
||||
{
|
||||
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifiers[modifier.GetType()]));
|
||||
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifierMap[modifier.GetType()]));
|
||||
}
|
||||
|
||||
return new NetData(player.PlayerId, bytes.ToArray());
|
||||
return new NetData(player.PlayerId, [.. bytes]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,26 @@
|
||||
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>
|
||||
/// 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;
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,60 @@
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using MiraAPI.Utilities;
|
||||
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; }
|
||||
|
||||
/// <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 (!Player.AmOwner)
|
||||
if (Player?.AmOwner == false)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -32,11 +71,14 @@ public abstract class TimedModifier : BaseModifier
|
||||
|
||||
if (RemoveOnComplete)
|
||||
{
|
||||
ModifierComponent.RpcRemoveModifier(Player, ModifierId);
|
||||
Player?.RpcRemoveModifier(ModifierId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Starts the timer.
|
||||
/// </summary>
|
||||
public void StartTimer()
|
||||
{
|
||||
if (TimerActive)
|
||||
@@ -48,4 +90,4 @@ public abstract class TimedModifier : BaseModifier
|
||||
TimerActive = true;
|
||||
TimeRemaining = Duration;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,186 @@
|
||||
using AmongUs.GameOptions;
|
||||
using Assets.CoreScripts;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
public static class CustomMurderRpcs
|
||||
{
|
||||
[MethodRpc((uint)MiraRpc.CustomMurder)]
|
||||
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 murderResultFlags2 = MurderResultFlags.DecisionByHost | murderResultFlags;
|
||||
|
||||
source.CustomMurder(target, murderResultFlags2, resetKillTimer, createDeadBody, teleportMurderer, showKillAnim, playKillSound);
|
||||
}
|
||||
|
||||
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;
|
||||
source.logger.Debug(string.Format("{0} trying to murder {1}", source.PlayerId, target.PlayerId), null);
|
||||
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);
|
||||
DestroyableSingleton<AchievementManager>.Instance.OnProtectACrewmate();
|
||||
}
|
||||
if (source.AmOwner || flag)
|
||||
{
|
||||
target.ShowFailedMurder();
|
||||
|
||||
if (resetKillTimer)
|
||||
{
|
||||
source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown) / 2f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
target.RemoveProtection();
|
||||
}
|
||||
source.logger.Debug(string.Format("{0} failed to murder {1} due to guardian angel protection", source.PlayerId, target.PlayerId), null);
|
||||
return;
|
||||
}
|
||||
if (resultFlags.HasFlag(MurderResultFlags.Succeeded) || resultFlags.HasFlag(MurderResultFlags.DecisionByHost))
|
||||
{
|
||||
DestroyableSingleton<DebugAnalytics>.Instance.Analytics.Kill(target.Data, source.Data);
|
||||
if (source.AmOwner)
|
||||
{
|
||||
if (GameManager.Instance.IsHideAndSeek())
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(StringNames.StatsImpostorKills_HideAndSeek);
|
||||
}
|
||||
else
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(StringNames.StatsImpostorKills);
|
||||
}
|
||||
if (source.CurrentOutfitType == PlayerOutfitType.Shapeshifted)
|
||||
{
|
||||
StatsManager.Instance.IncrementStat(StringNames.StatsShapeshifterShiftedKills);
|
||||
}
|
||||
if (Constants.ShouldPlaySfx() && playKillSound)
|
||||
{
|
||||
SoundManager.Instance.PlaySound(source.KillSfx, false, 0.8f, null);
|
||||
}
|
||||
|
||||
if (resetKillTimer)
|
||||
{
|
||||
source.SetKillTimer(GameOptionsManager.Instance.CurrentGameOptions.GetFloat(FloatOptionNames.KillCooldown));
|
||||
}
|
||||
}
|
||||
DestroyableSingleton<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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
if (showKillAnim)
|
||||
{
|
||||
DestroyableSingleton<HudManager>.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
|
||||
}
|
||||
|
||||
target.cosmetics.SetNameMask(false);
|
||||
target.RpcSetScanner(false);
|
||||
}
|
||||
DestroyableSingleton<AchievementManager>.Instance.OnMurder(source.AmOwner, target.AmOwner, source.CurrentOutfitType == PlayerOutfitType.Shapeshifted, source.shapeshiftTargetPlayerId, (int)target.PlayerId);
|
||||
Coroutines.Start(source.KillAnimations.Random().CoPerformCustomKill(source, target, createDeadBody, teleportMurderer));
|
||||
source.logger.Debug(string.Format("{0} succeeded in murdering {1}", source.PlayerId, target.PlayerId), null);
|
||||
}
|
||||
}
|
||||
|
||||
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.ToArray().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)
|
||||
{
|
||||
cam.Locked = true;
|
||||
ConsoleJoystick.SetMode_Task();
|
||||
if (PlayerControl.LocalPlayer.AmOwner)
|
||||
{
|
||||
PlayerControl.LocalPlayer.MyPhysics.inputHandler.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
target.Die(DeathReason.Kill, true);
|
||||
yield return source.MyPhysics.Animations.CoPlayCustomAnimation(anim.BlurAnim);
|
||||
sourcePhys.Animations.PlayIdleAnimation();
|
||||
|
||||
if (teleportMurderer)
|
||||
{
|
||||
source.NetTransform.SnapTo(target.transform.position);
|
||||
KillAnimation.SetMovement(source, true);
|
||||
}
|
||||
KillAnimation.SetMovement(target, true);
|
||||
|
||||
if (deadBody)
|
||||
{
|
||||
deadBody.enabled = true;
|
||||
}
|
||||
|
||||
if (isParticipant)
|
||||
{
|
||||
cam.Locked = false;
|
||||
PlayerControl.LocalPlayer.isKilling = false;
|
||||
source.isKilling = false;
|
||||
}
|
||||
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
@@ -6,5 +6,6 @@ public enum MiraRpc : uint
|
||||
SyncRoleOptions,
|
||||
AddModifier,
|
||||
RemoveModifier,
|
||||
SyncModifiers
|
||||
SyncModifiers,
|
||||
CustomMurder,
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using HarmonyLib;
|
||||
using InnerNet;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Roles;
|
||||
@@ -9,15 +8,22 @@ using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// General patches for the HudManager class.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(HudManager))]
|
||||
public static class HudManagerPatches
|
||||
{
|
||||
/// Custom buttons parent
|
||||
private static GameObject _bottomLeft;
|
||||
// Custom buttons parent.
|
||||
private static GameObject? _bottomLeft;
|
||||
|
||||
/// Custom role tab
|
||||
private static TaskPanelBehaviour _roleTab;
|
||||
// Custom role tab.
|
||||
private static TaskPanelBehaviour? _roleTab;
|
||||
|
||||
/// <summary>
|
||||
/// Update custom role tab and custom role hud elements.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The HudManager instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(HudManager.Update))]
|
||||
public static void UpdatePostfix(HudManager __instance)
|
||||
@@ -28,21 +34,25 @@ public static class HudManagerPatches
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
//CustomGameModeManager.ActiveMode?.HudUpdate(__instance);
|
||||
|
||||
if (local?.Data?.Role is null)
|
||||
// CustomGameModeManager.ActiveMode?.HudUpdate(__instance);
|
||||
|
||||
switch (local?.Data?.Role)
|
||||
{
|
||||
return;
|
||||
}
|
||||
case null:
|
||||
return;
|
||||
|
||||
if (local.Data.Role is ICustomRole customRole)
|
||||
{
|
||||
customRole.HudUpdate(__instance);
|
||||
|
||||
if (customRole.SetTabText() != null)
|
||||
case ICustomRole customRole:
|
||||
{
|
||||
if (!_roleTab)
|
||||
customRole.HudUpdate(__instance);
|
||||
|
||||
if (customRole.RoleHintType != RoleHintType.RoleTab)
|
||||
{
|
||||
_roleTab?.gameObject.Destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_roleTab == null)
|
||||
{
|
||||
_roleTab = CustomRoleManager.CreateRoleTab(customRole);
|
||||
}
|
||||
@@ -50,15 +60,16 @@ public static class HudManagerPatches
|
||||
{
|
||||
CustomRoleManager.UpdateRoleTab(_roleTab, customRole);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
else if (customRole.SetTabText() == null && _roleTab)
|
||||
{
|
||||
_roleTab.gameObject.Destroy();
|
||||
}
|
||||
}
|
||||
else if (_roleTab)
|
||||
{
|
||||
_roleTab.gameObject.Destroy();
|
||||
|
||||
default:
|
||||
if (_roleTab != null)
|
||||
{
|
||||
_roleTab.gameObject.Destroy();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,16 +85,17 @@ public static class HudManagerPatches
|
||||
}*/
|
||||
|
||||
/// <summary>
|
||||
/// Create custom buttons parent
|
||||
/// Create custom buttons and arrange them on the hud.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The HudManager instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(HudManager.Start))]
|
||||
public static void StartPostfix(HudManager __instance)
|
||||
{
|
||||
var buttons = __instance.transform.Find("Buttons");
|
||||
var bottomRight = buttons.Find("BottomRight");
|
||||
|
||||
if (!_bottomLeft)
|
||||
|
||||
if (_bottomLeft == null)
|
||||
{
|
||||
_bottomLeft = Object.Instantiate(bottomRight.gameObject, buttons);
|
||||
}
|
||||
@@ -106,14 +118,14 @@ public static class HudManagerPatches
|
||||
{
|
||||
ButtonLocation.BottomLeft => _bottomLeft.transform,
|
||||
ButtonLocation.BottomRight => bottomRight,
|
||||
_ => null
|
||||
_ => null,
|
||||
};
|
||||
|
||||
if (location is null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
button.CreateButton(location);
|
||||
}
|
||||
|
||||
@@ -124,30 +136,34 @@ public static class HudManagerPatches
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Make sure all launchpad hud elements are inactive/active when appropriate
|
||||
/// Set the custom role tab and custom buttons active when the hud is active.
|
||||
/// </summary>
|
||||
/// <param name="__instance">HudManager instance.</param>
|
||||
/// <param name="localPlayer">The local PlayerControl.</param>
|
||||
/// <param name="role">The player's RoleBehaviour.</param>
|
||||
/// <param name="isActive">Whether the Hud should be set active or not.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(HudManager.SetHudActive), typeof(PlayerControl), typeof(RoleBehaviour), typeof(bool))]
|
||||
public static void SetHudActivePostfix(HudManager __instance, [HarmonyArgument(0)] PlayerControl player, [HarmonyArgument(1)] RoleBehaviour roleBehaviour, [HarmonyArgument(2)] bool isActive)
|
||||
public static void SetHudActivePostfix(HudManager __instance, PlayerControl localPlayer, RoleBehaviour role, bool isActive)
|
||||
{
|
||||
if (!player.Data)
|
||||
if (!localPlayer.Data)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_roleTab)
|
||||
{
|
||||
_roleTab.gameObject.SetActive(isActive);
|
||||
_roleTab?.gameObject.SetActive(isActive);
|
||||
}
|
||||
|
||||
foreach (var button in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
button.SetActive(isActive, roleBehaviour);
|
||||
button.SetActive(isActive, role);
|
||||
}
|
||||
|
||||
if (roleBehaviour is ICustomRole role)
|
||||
if (role is ICustomRole customRole)
|
||||
{
|
||||
__instance.ImpostorVentButton.gameObject.SetActive(isActive && role.CanUseVent);
|
||||
__instance.ImpostorVentButton.gameObject.SetActive(isActive && customRole.CanUseVent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public static class IntroCutscenePatches
|
||||
{
|
||||
if (PlayerControl.LocalPlayer.Data.Role is ICustomRole customRole)
|
||||
{
|
||||
if (!customRole.IsNeutral)
|
||||
if (customRole.Team is not ModdedRoleTeams.Neutral)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,28 @@ using MiraAPI.Hud;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// Harmony patches for the MeetingHud class.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(MeetingHud))]
|
||||
public static class MeetingHudPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets the cooldown and effect of all custom buttons when the meeting starts.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(MeetingHud.Start))]
|
||||
public static void StartPostfix()
|
||||
{
|
||||
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
customActionButton.ResetCooldownAndOrEffect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the cooldown and effect of all custom buttons when the voting is complete.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(MeetingHud.VotingComplete))]
|
||||
public static void VotingCompletePostfix()
|
||||
@@ -15,4 +34,4 @@ public static class MeetingHudPatches
|
||||
customActionButton.ResetCooldownAndOrEffect();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,15 +10,21 @@ using UnityEngine.Events;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Patches the GameOptionsMenu to add custom options.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(GameOptionsMenu))]
|
||||
public static class GameOptionsMenuPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Update patch for the GameOptionsMenu.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The GameOptionsMenu instance.</param>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
|
||||
public static void UpdatePatch(GameOptionsMenu __instance)
|
||||
{
|
||||
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -71,7 +77,7 @@ public static class GameOptionsMenuPatch
|
||||
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
|
||||
public static bool SettingsPatch(GameOptionsMenu __instance)
|
||||
{
|
||||
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using HarmonyLib;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
@@ -13,17 +12,30 @@ using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Patches for the <see cref="GameSettingMenu"/> to add support for custom options.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(GameSettingMenu))]
|
||||
public static class GameSettingMenuPatches
|
||||
{
|
||||
public static int CurrentSelectedMod { get; private set; }
|
||||
/// <summary>
|
||||
/// Gets the currently selected mod index.
|
||||
/// </summary>
|
||||
public static int SelectedModIdx { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently selected mod.
|
||||
/// </summary>
|
||||
public static MiraPluginInfo? SelectedMod { get; private set; }
|
||||
|
||||
private static TextMeshPro? _text;
|
||||
|
||||
private static Vector3 _roleBtnOgPos;
|
||||
|
||||
/// <summary>
|
||||
/// Prefix for the <see cref="GameSettingMenu.Start"/> method. Sets up the custom options.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The GameSettingMenu instance.</param>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(GameSettingMenu.Start))]
|
||||
public static void StartPrefix(GameSettingMenu __instance)
|
||||
@@ -57,11 +69,12 @@ public static class GameSettingMenuPatches
|
||||
passiveButton.OnClick = new ButtonClickedEvent();
|
||||
passiveButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
if (CurrentSelectedMod != MiraPluginManager.Instance.RegisteredPlugins.Count)
|
||||
SelectedModIdx += 1;
|
||||
if (SelectedModIdx > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
{
|
||||
CurrentSelectedMod += 1;
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
SelectedModIdx = 0;
|
||||
}
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
|
||||
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
|
||||
@@ -71,17 +84,18 @@ public static class GameSettingMenuPatches
|
||||
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX = backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
||||
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
if (CurrentSelectedMod != 0)
|
||||
SelectedModIdx -= 1;
|
||||
if (SelectedModIdx < 0)
|
||||
{
|
||||
CurrentSelectedMod -= 1;
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
SelectedModIdx = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
}
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
}
|
||||
|
||||
private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles)
|
||||
{
|
||||
if (_text is not null && CurrentSelectedMod == 0)
|
||||
if (_text is not null && SelectedModIdx == 0)
|
||||
{
|
||||
_text.text = "Default";
|
||||
_text.fontSizeMax = 3.2f;
|
||||
@@ -89,22 +103,17 @@ public static class GameSettingMenuPatches
|
||||
else if (_text is not null)
|
||||
{
|
||||
_text.fontSizeMax = 2.3f;
|
||||
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod - 1).Value;
|
||||
if (SelectedMod == null)
|
||||
{
|
||||
CurrentSelectedMod = 0;
|
||||
UpdateText(menu, settings, roles);
|
||||
}
|
||||
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins()[SelectedModIdx - 1];
|
||||
|
||||
var name = SelectedMod?.MiraPlugin.OptionsTitleText;
|
||||
_text.text = name?[..Math.Min(name.Length, 25)];
|
||||
var name = SelectedMod.MiraPlugin.OptionsTitleText;
|
||||
_text.text = name[..Math.Min(name.Length, 25)];
|
||||
}
|
||||
|
||||
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
|
||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
|
||||
if (CurrentSelectedMod != 0)
|
||||
if (SelectedModIdx != 0)
|
||||
{
|
||||
if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0)
|
||||
{
|
||||
@@ -122,7 +131,7 @@ public static class GameSettingMenuPatches
|
||||
}
|
||||
}
|
||||
|
||||
if (roles?.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0)
|
||||
if (roles.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0)
|
||||
{
|
||||
if (roles.advancedSettingChildren is not null)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
@@ -17,10 +16,9 @@ namespace MiraAPI.Patches.Options;
|
||||
[HarmonyPatch(typeof(LobbyViewSettingsPane))]
|
||||
public static class LobbyViewPanePatches
|
||||
{
|
||||
|
||||
public static int CurrentSelectedMod { get; private set; }
|
||||
|
||||
public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod-1).Value;
|
||||
public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins()[CurrentSelectedMod-1];
|
||||
|
||||
|
||||
[HarmonyPostfix]
|
||||
@@ -49,7 +47,7 @@ public static class LobbyViewPanePatches
|
||||
passiveButton.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
CurrentSelectedMod += 1;
|
||||
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count)
|
||||
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins().Length)
|
||||
{
|
||||
CurrentSelectedMod = 0;
|
||||
}
|
||||
@@ -72,7 +70,7 @@ public static class LobbyViewPanePatches
|
||||
CurrentSelectedMod -= 1;
|
||||
if (CurrentSelectedMod < 0)
|
||||
{
|
||||
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count;
|
||||
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins().Length;
|
||||
}
|
||||
__instance.RefreshTab();
|
||||
__instance.scrollBar.ScrollToTop();
|
||||
@@ -186,146 +184,145 @@ public static class LobbyViewPanePatches
|
||||
|
||||
private static void DrawRolesTab(LobbyViewSettingsPane instance)
|
||||
{
|
||||
if (SelectedMod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (SelectedMod == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var num = 0.95f;
|
||||
var num2 = -6.53f;
|
||||
var categoryHeaderMasked =
|
||||
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
|
||||
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
|
||||
var num = 0.95f;
|
||||
var num2 = -6.53f;
|
||||
var categoryHeaderMasked =
|
||||
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
|
||||
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
|
||||
|
||||
var list = new List<Type>();
|
||||
var list = new List<Type>();
|
||||
|
||||
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
|
||||
{
|
||||
var filteredRoles = SelectedMod.CustomRoles.Values
|
||||
.OfType<ICustomRole>()
|
||||
.Where(x => !x.HideSettings && x.Team == team).ToList();
|
||||
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
|
||||
{
|
||||
var filteredRoles = SelectedMod.CustomRoles.Values
|
||||
.OfType<ICustomRole>()
|
||||
.Where(x => !x.HideSettings && x.Team == team).ToList();
|
||||
|
||||
if (filteredRoles.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (filteredRoles.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var categoryHeaderRoleVariant =
|
||||
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
|
||||
var categoryHeaderRoleVariant =
|
||||
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
|
||||
|
||||
switch (team)
|
||||
{
|
||||
case ModdedRoleTeams.Crewmate:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
|
||||
break;
|
||||
case ModdedRoleTeams.Impostor:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
|
||||
break;
|
||||
case ModdedRoleTeams.Neutral:
|
||||
default:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
|
||||
categoryHeaderRoleVariant.Title.text = team + " Roles";
|
||||
categoryHeaderRoleVariant.Background.color = Color.gray;
|
||||
categoryHeaderRoleVariant.Title.color = Color.white;
|
||||
break;
|
||||
}
|
||||
switch (team)
|
||||
{
|
||||
case ModdedRoleTeams.Crewmate:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
|
||||
break;
|
||||
case ModdedRoleTeams.Impostor:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
|
||||
break;
|
||||
default:
|
||||
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
|
||||
categoryHeaderRoleVariant.Title.text = team + " Roles";
|
||||
categoryHeaderRoleVariant.Background.color = Color.gray;
|
||||
categoryHeaderRoleVariant.Title.color = Color.white;
|
||||
break;
|
||||
}
|
||||
|
||||
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
|
||||
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
||||
num -= 0.696f;
|
||||
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
|
||||
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
||||
num -= 0.696f;
|
||||
|
||||
foreach (var customRole in filteredRoles)
|
||||
{
|
||||
var roleBehaviour = customRole as RoleBehaviour;
|
||||
if (roleBehaviour == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
foreach (var customRole in filteredRoles)
|
||||
{
|
||||
var roleBehaviour = customRole as RoleBehaviour;
|
||||
if (roleBehaviour == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var chancePerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
|
||||
var numPerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
|
||||
var chancePerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
|
||||
var numPerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
|
||||
|
||||
var flag = numPerGame == 0;
|
||||
var flag = numPerGame == 0;
|
||||
|
||||
var viewSettingsInfoPanelRoleVariant =
|
||||
Object.Instantiate(
|
||||
instance.infoPanelRoleOrigin, instance.settingsContainer, true);
|
||||
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
|
||||
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
|
||||
var viewSettingsInfoPanelRoleVariant =
|
||||
Object.Instantiate(
|
||||
instance.infoPanelRoleOrigin, instance.settingsContainer, true);
|
||||
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
|
||||
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
|
||||
|
||||
var advancedRoleOptions = SelectedMod.Options
|
||||
.Where(x => x.AdvancedRole == customRole.GetType())
|
||||
.ToList();
|
||||
var advancedRoleOptions = SelectedMod.Options
|
||||
.Where(x => x.AdvancedRole == customRole.GetType())
|
||||
.ToList();
|
||||
|
||||
if (!flag && advancedRoleOptions.Count > 0)
|
||||
{
|
||||
list.Add(customRole.GetType());
|
||||
}
|
||||
if (!flag && advancedRoleOptions.Count > 0)
|
||||
{
|
||||
list.Add(customRole.GetType());
|
||||
}
|
||||
|
||||
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
|
||||
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
|
||||
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
|
||||
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
|
||||
|
||||
viewSettingsInfoPanelRoleVariant.titleText.color =
|
||||
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
|
||||
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
|
||||
viewSettingsInfoPanelRoleVariant.background.color =
|
||||
customRole.RoleColor.GetAlternateColor();
|
||||
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
|
||||
num -= 0.664f;
|
||||
}
|
||||
viewSettingsInfoPanelRoleVariant.titleText.color =
|
||||
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
|
||||
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
|
||||
viewSettingsInfoPanelRoleVariant.background.color =
|
||||
customRole.RoleColor.GetAlternateColor();
|
||||
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
|
||||
num -= 0.664f;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
var categoryHeaderMasked2 =
|
||||
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
|
||||
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
|
||||
categoryHeaderMasked2.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
|
||||
num -= 1.7f;
|
||||
var num3 = 0f;
|
||||
for (var k = 0; k < list.Count; k++)
|
||||
{
|
||||
float num4;
|
||||
if (k % 2 == 0)
|
||||
{
|
||||
num4 = -5.8f;
|
||||
if (k > 0)
|
||||
{
|
||||
num -= num3 + 0.59f;
|
||||
num3 = 0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
num4 = 0.14999962f;
|
||||
}
|
||||
if (list.Count > 0)
|
||||
{
|
||||
var categoryHeaderMasked2 =
|
||||
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
|
||||
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
|
||||
categoryHeaderMasked2.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
|
||||
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
|
||||
num -= 1.7f;
|
||||
var num3 = 0f;
|
||||
for (var k = 0; k < list.Count; k++)
|
||||
{
|
||||
float num4;
|
||||
if (k % 2 == 0)
|
||||
{
|
||||
num4 = -5.8f;
|
||||
if (k > 0)
|
||||
{
|
||||
num -= num3 + 0.59f;
|
||||
num3 = 0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
num4 = 0.14999962f;
|
||||
}
|
||||
|
||||
var advancedRoleViewPanel =
|
||||
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
|
||||
advancedRoleViewPanel.transform.localScale = Vector3.one;
|
||||
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
|
||||
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
|
||||
var advancedRoleViewPanel =
|
||||
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
|
||||
advancedRoleViewPanel.transform.localScale = Vector3.one;
|
||||
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
|
||||
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
|
||||
|
||||
if (num5 > num3)
|
||||
{
|
||||
num3 = num5;
|
||||
}
|
||||
if (num5 > num3)
|
||||
{
|
||||
num3 = num5;
|
||||
}
|
||||
|
||||
instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
|
||||
}
|
||||
}
|
||||
instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
instance.scrollBar.SetYBoundsMax(-num);
|
||||
instance.scrollBar.SetYBoundsMax(-num);
|
||||
}
|
||||
|
||||
private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer)
|
||||
|
||||
@@ -16,8 +16,8 @@ public class NotificationPopperPatch
|
||||
{
|
||||
if (CustomRoleManager.CustomRoles.Values.Any(role => role.StringName == key))
|
||||
{
|
||||
string item = string.Empty;
|
||||
string text = (teamType == RoleTeamTypes.Crewmate) ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor();
|
||||
var item = string.Empty;
|
||||
var text = (teamType == RoleTeamTypes.Crewmate) ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor();
|
||||
item = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.LobbyChangeSettingNotificationRole, new Object[]
|
||||
{
|
||||
string.Concat(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -8,7 +9,6 @@ using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
@@ -30,7 +30,7 @@ public static class RoleSettingMenuPatches
|
||||
var maskBg = __instance.scrollBar.transform.FindChild("MaskBg");
|
||||
var hitbox = __instance.scrollBar.transform.FindChild("Hitbox");
|
||||
|
||||
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
{
|
||||
__instance.AllButton.transform.parent.gameObject.SetActive(true);
|
||||
__instance.AllButton.gameObject.SetActive(true);
|
||||
@@ -127,7 +127,7 @@ public static class RoleSettingMenuPatches
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.Update))]
|
||||
public static bool UpdatePatch()
|
||||
{
|
||||
return GameSettingMenuPatches.CurrentSelectedMod == 0;
|
||||
return GameSettingMenuPatches.SelectedModIdx == 0;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
@@ -224,7 +224,7 @@ public static class RoleSettingMenuPatches
|
||||
__instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
|
||||
__instance.roleHeaderSprite.color = customRole.RoleColor;
|
||||
__instance.roleHeaderText.color = customRole.RoleColor.GetAlternateColor();
|
||||
|
||||
|
||||
var bg = __instance.AdvancedRolesSettings.transform.Find("Background");
|
||||
bg.localPosition = new Vector3(1.4041f, -7.08f, 0);
|
||||
bg.GetComponent<SpriteRenderer>().size = new Vector2(89.4628f, 100);
|
||||
@@ -237,7 +237,7 @@ public static class RoleSettingMenuPatches
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
optionBehaviour.OnValueChanged = new Action<OptionBehaviour>(__instance.ValueChanged);
|
||||
if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
@@ -263,7 +263,7 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
|
||||
roleOptionSetting.titleText.color = customRole.RoleColor.GetAlternateColor();
|
||||
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
||||
|
||||
|
||||
if (GameSettingMenuPatches.SelectedMod is null ||
|
||||
GameSettingMenuPatches.SelectedMod.Options.Any(x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role)))
|
||||
{
|
||||
|
||||
@@ -1,17 +1,32 @@
|
||||
using HarmonyLib;
|
||||
using System.Globalization;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
/// <summary>
|
||||
/// Patches for the various game settings.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public class SettingPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Prefix for the <see cref="FloatGameSetting.GetValueString"/> method. Adds support for custom number suffixes.
|
||||
/// </summary>
|
||||
/// <param name="__instance">The FloatGameSetting instance.</param>
|
||||
/// <param name="__result">The result of the GetValueString method.</param>
|
||||
/// <param name="value">The float value.</param>
|
||||
/// <returns>False to skip original method.</returns>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))]
|
||||
public static bool ValueStringPatch(FloatGameSetting __instance, ref string __result, [HarmonyArgument(0)] float value)
|
||||
public static bool ValueStringPatch(
|
||||
FloatGameSetting __instance,
|
||||
ref string __result,
|
||||
[HarmonyArgument(0)] float value)
|
||||
{
|
||||
string result = string.Empty;
|
||||
MiraNumberSuffixes suffix = (MiraNumberSuffixes)__instance.SuffixType;
|
||||
var result = string.Empty;
|
||||
var suffix = (MiraNumberSuffixes)__instance.SuffixType;
|
||||
|
||||
if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
|
||||
{
|
||||
@@ -31,12 +46,15 @@ public class SettingPatches
|
||||
}
|
||||
else
|
||||
{
|
||||
result = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameSecondsAbbrev, (Il2CppSystem.Object[])(
|
||||
[
|
||||
value.ToString(__instance.FormatString)
|
||||
]));
|
||||
result = DestroyableSingleton<TranslationController>.Instance.GetString(
|
||||
StringNames.GameSecondsAbbrev,
|
||||
(Il2CppSystem.Object[])
|
||||
[
|
||||
value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)
|
||||
]);
|
||||
}
|
||||
|
||||
__result = result;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,20 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities.Extensions;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// General patches for the PlayerControl class.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(PlayerControl))]
|
||||
public static class PlayerControlPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the modifier component to the player on start.
|
||||
/// </summary>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerControl.Start))]
|
||||
public static void PlayerControlStartPostfix(PlayerControl __instance)
|
||||
@@ -21,6 +27,24 @@ public static class PlayerControlPatches
|
||||
__instance.gameObject.AddComponent<ModifierComponent>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the OnDeath method for all active modifiers.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerControl.Die))]
|
||||
public static void PlayerControlDiePostfix(PlayerControl __instance)
|
||||
{
|
||||
var modifiersComponent = __instance.GetComponent<ModifierComponent>();
|
||||
|
||||
if (modifiersComponent)
|
||||
{
|
||||
modifiersComponent.ActiveModifiers.ForEach(x=>x.OnDeath());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// FixedUpdate handler for custom roles and custom buttons.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
|
||||
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
|
||||
@@ -29,26 +53,25 @@ public static class PlayerControlPatches
|
||||
{
|
||||
customRole.PlayerControlFixedUpdate(__instance);
|
||||
}
|
||||
|
||||
|
||||
if (!__instance.AmOwner)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach (var button in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
if (__instance.Data?.Role == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!button.Enabled(__instance.Data?.Role))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
button.FixedUpdateHandler(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.Roles;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
@@ -23,6 +22,5 @@ public static class GameStartupPatch
|
||||
|
||||
_runOnce = true;
|
||||
CustomRoleManager.RegisterInRoleManager();
|
||||
PaletteManager.RegisterAllColors();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ public static class RoleOptionsCollectionPatch
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
if (customRole.HideSettings)
|
||||
{
|
||||
return false;
|
||||
@@ -27,7 +27,6 @@ public static class RoleOptionsCollectionPatch
|
||||
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.ChanceConfigDefinition, out var entry);
|
||||
__result = entry.Value;
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using Il2CppSystem.Text;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
@@ -34,17 +33,17 @@ public static class TaskAdderPatch
|
||||
|
||||
GameObject hitbox = new("Hitbox")
|
||||
{
|
||||
layer = 5
|
||||
layer = 5,
|
||||
};
|
||||
hitbox.transform.SetParent(__instance.TaskParent.transform, false);
|
||||
hitbox.transform.localScale = new Vector3(7.5f, 6.5f, 1);
|
||||
hitbox.transform.localPosition = new Vector3(2.8f, -2.2f, 0);
|
||||
|
||||
SpriteMask mask = hitbox.AddComponent<SpriteMask>();
|
||||
|
||||
var mask = hitbox.AddComponent<SpriteMask>();
|
||||
mask.sprite = MiraAssets.NextButton.LoadAsset();
|
||||
mask.alphaCutoff = 0.0f;
|
||||
|
||||
BoxCollider2D collider = hitbox.AddComponent<BoxCollider2D>();
|
||||
|
||||
var collider = hitbox.AddComponent<BoxCollider2D>();
|
||||
collider.size = new Vector2(1f, 1f);
|
||||
collider.enabled = true;
|
||||
|
||||
@@ -54,7 +53,7 @@ public static class TaskAdderPatch
|
||||
__instance.RoleButton.GetComponent<PassiveButton>().ClickMask = collider;
|
||||
__instance.RootFolderPrefab.GetComponent<PassiveButton>().ClickMask = collider;
|
||||
__instance.RootFolderPrefab.gameObject.SetActive(false);
|
||||
|
||||
|
||||
__instance.TaskParent = inner.transform;
|
||||
|
||||
_rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
@@ -63,7 +62,7 @@ public static class TaskAdderPatch
|
||||
_rolesFolder.FolderName = "Roles";
|
||||
_rolesFolder.name = "RolesFolder";
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values)
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
{
|
||||
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
|
||||
newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
|
||||
@@ -85,14 +84,14 @@ public static class TaskAdderPatch
|
||||
[HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)]
|
||||
public static void RoleGetterPatch(TaskAddButton __instance)
|
||||
{
|
||||
if (__instance.role is ICustomRole { IsNeutral: true })
|
||||
if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Neutral })
|
||||
{
|
||||
__instance.FileImage.color = Color.gray;
|
||||
}
|
||||
|
||||
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
|
||||
}
|
||||
|
||||
|
||||
private static void AddFileAsChildCustom(this TaskAdderGame instance, TaskFolder taskFolder, TaskAddButton item, ref float xCursor, ref float yCursor, ref float maxHeight)
|
||||
{
|
||||
item.transform.SetParent(instance.TaskParent);
|
||||
@@ -115,7 +114,7 @@ public static class TaskAdderPatch
|
||||
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
|
||||
public static bool ShowPatch(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder)
|
||||
{
|
||||
var stringBuilder = new StringBuilder(64);
|
||||
var stringBuilder = new Il2CppSystem.Text.StringBuilder(64);
|
||||
__instance.Hierarchy.Add(taskFolder);
|
||||
for (var i = 0; i < __instance.Hierarchy.Count; i++)
|
||||
{
|
||||
@@ -286,4 +285,4 @@ public static class TaskAdderPatch
|
||||
ControllerManager.Instance.SetBackButton(__instance.FolderBackButton);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,44 @@ using System.Collections.Generic;
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
public class MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info)
|
||||
/// <summary>
|
||||
/// Represents a Mira plugin.
|
||||
/// </summary>
|
||||
public class MiraPluginInfo
|
||||
{
|
||||
public string PluginId { get; set; } = info.Metadata.GUID;
|
||||
public IMiraPlugin MiraPlugin { get; set; } = miraPlugin;
|
||||
public PluginInfo PluginInfo { get; set; } = info;
|
||||
public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile();
|
||||
internal MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info)
|
||||
{
|
||||
MiraPlugin = miraPlugin;
|
||||
PluginConfig = miraPlugin.GetConfigFile();
|
||||
PluginInfo = info;
|
||||
PluginId = info.Metadata.GUID;
|
||||
}
|
||||
|
||||
public readonly List<AbstractOptionGroup> OptionGroups = [];
|
||||
public readonly List<IModdedOption> Options = [];
|
||||
internal List<AbstractOptionGroup> OptionGroups { get; } = [];
|
||||
|
||||
public readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
|
||||
internal readonly Dictionary<int, CustomGameMode> GameModes = [];
|
||||
}
|
||||
internal List<IModdedOption> Options { get; } = [];
|
||||
|
||||
internal Dictionary<ushort, RoleBehaviour> CustomRoles { get; } = [];
|
||||
|
||||
internal Dictionary<int, CustomGameMode> GameModes { get; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's ID, as defined in the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
public string PluginId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's instance as an <see cref="IMiraPlugin"/>.
|
||||
/// </summary>
|
||||
public IMiraPlugin MiraPlugin { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's BepInEx metadata.
|
||||
/// </summary>
|
||||
public PluginInfo PluginInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the plugin's configuration file.
|
||||
/// </summary>
|
||||
public ConfigFile PluginConfig { get; }
|
||||
}
|
||||
@@ -1,61 +1,58 @@
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using MiraAPI.Colors;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
internal class MiraPluginManager
|
||||
{
|
||||
public readonly Dictionary<Assembly, MiraPluginInfo> RegisteredPlugins = [];
|
||||
private readonly Dictionary<Assembly, MiraPluginInfo> _registeredPlugins = [];
|
||||
|
||||
private static MiraPluginManager _instance;
|
||||
internal MiraPluginInfo[] RegisteredPlugins() => [.. _registeredPlugins.Values];
|
||||
|
||||
public static MiraPluginManager Instance
|
||||
{
|
||||
get => _instance ??= new MiraPluginManager();
|
||||
private set => _instance = value;
|
||||
}
|
||||
public static MiraPluginManager Instance { get; private set; } = new();
|
||||
|
||||
internal void Initialize()
|
||||
{
|
||||
Instance = this;
|
||||
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
|
||||
{
|
||||
if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin)))
|
||||
if (plugin is not IMiraPlugin miraPlugin)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo);
|
||||
var info = new MiraPluginInfo(miraPlugin, pluginInfo);
|
||||
|
||||
RegisterModifierAttribute(assembly);
|
||||
RegisterAllOptions(assembly, info);
|
||||
|
||||
|
||||
RegisterRoleAttribute(assembly, info);
|
||||
RegisterButtonAttribute(assembly);
|
||||
|
||||
RegisterColorClasses(assembly);
|
||||
|
||||
RegisteredPlugins.Add(assembly, info);
|
||||
_registeredPlugins.Add(assembly, info);
|
||||
|
||||
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
|
||||
};
|
||||
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
|
||||
}
|
||||
|
||||
public MiraPluginInfo GetPluginByGuid(string guid)
|
||||
{
|
||||
return RegisteredPlugins.Values.First(plugin => plugin.PluginId == guid);
|
||||
return _registeredPlugins.Values.First(plugin => plugin.PluginId == guid);
|
||||
}
|
||||
|
||||
private static void RegisterAllOptions(Assembly assembly, MiraPluginInfo pluginInfo)
|
||||
@@ -68,7 +65,7 @@ internal class MiraPluginManager
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
foreach (var property in type.GetProperties())
|
||||
{
|
||||
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
|
||||
@@ -76,7 +73,7 @@ internal class MiraPluginManager
|
||||
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
|
||||
if (attribute == null)
|
||||
{
|
||||
@@ -98,22 +95,22 @@ internal class MiraPluginManager
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Custom roles are only supported on all clients.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
roles.Add(type);
|
||||
}
|
||||
|
||||
|
||||
CustomRoleManager.RegisterRoleTypes(roles, pluginInfo);
|
||||
}
|
||||
|
||||
@@ -138,14 +135,13 @@ internal class MiraPluginManager
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var color = (CustomColor)property.GetValue(null);
|
||||
if (color == null)
|
||||
|
||||
if (property.GetValue(null) is not CustomColor color)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Color property {property.Name} in {type.Name} is null.");
|
||||
Logger<MiraApiPlugin>.Error($"Color property {property.Name} in {type.Name} is not a CustomColor.");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
PaletteManager.CustomColors.Add(color);
|
||||
}
|
||||
}
|
||||
@@ -163,7 +159,6 @@ internal class MiraPluginManager
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static void RegisterButtonAttribute(Assembly assembly)
|
||||
{
|
||||
foreach (var type in assembly.GetTypes())
|
||||
@@ -175,4 +170,4 @@ internal class MiraPluginManager
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
@@ -9,22 +11,22 @@ using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using MiraAPI.Utilities;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Custom role manager for handling custom roles.
|
||||
/// </summary>
|
||||
public static class CustomRoleManager
|
||||
{
|
||||
public static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = new();
|
||||
|
||||
public static readonly Dictionary<Type, ushort> RoleIds = new();
|
||||
|
||||
internal static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
|
||||
internal static readonly Dictionary<Type, ushort> RoleIds = [];
|
||||
|
||||
private static ushort _roleId = 100;
|
||||
|
||||
|
||||
private static ushort GetNextRoleId()
|
||||
{
|
||||
return _roleId++;
|
||||
@@ -35,10 +37,10 @@ public static class CustomRoleManager
|
||||
RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray();
|
||||
}
|
||||
|
||||
public static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo)
|
||||
internal static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo)
|
||||
{
|
||||
roles.ForEach(x=>RoleIds.Add(x, GetNextRoleId()));
|
||||
|
||||
roles.ForEach(x => RoleIds.Add(x, GetNextRoleId()));
|
||||
|
||||
foreach (var roleType in roles)
|
||||
{
|
||||
ClassInjector.RegisterTypeInIl2Cpp(roleType);
|
||||
@@ -53,11 +55,11 @@ public static class CustomRoleManager
|
||||
}
|
||||
}
|
||||
|
||||
private static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMod)
|
||||
private static RoleBehaviour? RegisterRole(Type roleType, MiraPluginInfo parentMod)
|
||||
{
|
||||
if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{roleType?.Name} does not inherit from RoleBehaviour or ICustomRole.");
|
||||
Logger<MiraApiPlugin>.Error($"{roleType.Name} does not inherit from RoleBehaviour or ICustomRole.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -68,7 +70,7 @@ public static class CustomRoleManager
|
||||
roleBehaviour.gameObject.Destroy();
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
var roleId = RoleIds[roleType];
|
||||
|
||||
roleBehaviour.Role = (RoleTypes)roleId;
|
||||
@@ -83,7 +85,7 @@ public static class CustomRoleManager
|
||||
roleBehaviour.TasksCountTowardProgress = customRole.TasksCount;
|
||||
roleBehaviour.CanVent = customRole.CanUseVent;
|
||||
roleBehaviour.DefaultGhostRole = customRole.GhostRole;
|
||||
roleBehaviour.MaxCount = 15;
|
||||
roleBehaviour.MaxCount = customRole.MaxPlayers;
|
||||
roleBehaviour.RoleScreenshot = customRole.OptionsScreenshot.LoadAsset();
|
||||
|
||||
if (customRole.IsGhostRole)
|
||||
@@ -91,6 +93,14 @@ public static class CustomRoleManager
|
||||
RoleManager.GhostRoles.Add(roleBehaviour.Role);
|
||||
}
|
||||
|
||||
var useTaskHint = customRole.RoleHintType is RoleHintType.TaskHint;
|
||||
var overridesTaskText = customRole.GetType().GetMethod("SpawnTaskHeader")?.DeclaringType == customRole.GetType();
|
||||
|
||||
if (useTaskHint && !overridesTaskText)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Role {customRole.RoleName} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!");
|
||||
}
|
||||
|
||||
CustomRoles.Add(roleId, roleBehaviour);
|
||||
|
||||
if (customRole.HideSettings)
|
||||
@@ -101,16 +111,27 @@ public static class CustomRoleManager
|
||||
var config = parentMod.PluginConfig;
|
||||
config.Bind(customRole.NumConfigDefinition, 1);
|
||||
config.Bind(customRole.ChanceConfigDefinition, 100);
|
||||
|
||||
|
||||
return roleBehaviour;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds the parent mod of a custom role.
|
||||
/// </summary>
|
||||
/// <param name="role">The ICustomRole object.</param>
|
||||
/// <returns>A MiraPluginInfo object representing the parent mod of the role.</returns>
|
||||
public static MiraPluginInfo FindParentMod(ICustomRole role)
|
||||
{
|
||||
return MiraPluginManager.Instance.RegisteredPlugins.First(plugin => plugin.Value.CustomRoles.ContainsValue(role as RoleBehaviour)).Value;
|
||||
return MiraPluginManager.Instance.RegisteredPlugins().First(plugin => plugin.CustomRoles.ContainsValue(role as RoleBehaviour ?? throw new InvalidOperationException()));
|
||||
}
|
||||
|
||||
public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole result)
|
||||
/// <summary>
|
||||
/// Gets a custom role behaviour by role type.
|
||||
/// </summary>
|
||||
/// <param name="roleType">The role type enum.</param>
|
||||
/// <param name="result">The ICustomRole result.</param>
|
||||
/// <returns>True if the role was found.</returns>
|
||||
public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole? result)
|
||||
{
|
||||
CustomRoles.TryGetValue((ushort)roleType, out var temp);
|
||||
if (temp)
|
||||
@@ -156,8 +177,8 @@ public static class CustomRoleManager
|
||||
|
||||
panel.SetTaskText(role.SetTabText().ToString());
|
||||
}
|
||||
|
||||
internal static void SyncAllRoleSettings(int targetId=-1)
|
||||
|
||||
internal static void SyncAllRoleSettings(int targetId = -1)
|
||||
{
|
||||
var data = CustomRoles.Values
|
||||
.Where(x => x is ICustomRole { HideSettings: false })
|
||||
@@ -172,6 +193,16 @@ public static class CustomRoleManager
|
||||
|
||||
internal static void HandleSyncRoleOptions(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 (!CustomRoles.TryGetValue((ushort)netData.Id, out var role))
|
||||
@@ -187,14 +218,16 @@ public static class CustomRoleManager
|
||||
|
||||
var num = BitConverter.ToInt32(netData.Data, 0);
|
||||
var chance = BitConverter.ToInt32(netData.Data, 4);
|
||||
|
||||
|
||||
DestroyableSingleton<HudManager>.Instance.Notifier.AddRoleSettingsChangeMessage(role.StringName, num, chance, role.TeamType, false);
|
||||
|
||||
try
|
||||
{
|
||||
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.NumConfigDefinition,
|
||||
customRole.ParentMod.PluginConfig.TryGetEntry<int>(
|
||||
customRole.NumConfigDefinition,
|
||||
out var numEntry);
|
||||
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.ChanceConfigDefinition,
|
||||
customRole.ParentMod.PluginConfig.TryGetEntry<int>(
|
||||
customRole.ChanceConfigDefinition,
|
||||
out var chanceEntry);
|
||||
|
||||
numEntry.Value = num;
|
||||
@@ -206,9 +239,15 @@ public static class CustomRoleManager
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins())
|
||||
{
|
||||
plugin.PluginConfig.Save();
|
||||
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
|
||||
}
|
||||
|
||||
if (LobbyInfoPane.Instance)
|
||||
{
|
||||
LobbyInfoPane.Instance.RefreshPane();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+107
-38
@@ -1,92 +1,161 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System.Text;
|
||||
using AmongUs.GameOptions;
|
||||
using BepInEx.Configuration;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using System;
|
||||
using System.Text;
|
||||
using MiraAPI.PluginLoading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for custom roles.
|
||||
/// </summary>
|
||||
public interface ICustomRole
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the name of the role.
|
||||
/// </summary>
|
||||
string RoleName { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the description of the role. Used in the Intro Cutscene.
|
||||
/// </summary>
|
||||
string RoleDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the long description of the role. Used in the Role Tab and Role Options.
|
||||
/// </summary>
|
||||
string RoleLongDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color of the role.
|
||||
/// </summary>
|
||||
Color RoleColor { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the team of the role.
|
||||
/// </summary>
|
||||
ModdedRoleTeams Team { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the **maximum amount** of players that can have this role at a time. This is not the same as the **number** of players that can have this role in a game.
|
||||
/// </summary>
|
||||
int MaxPlayers => 15;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sprite used for the Role Options menu screenshot.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Sprite used for the Role Icon.
|
||||
/// </summary>
|
||||
[HideFromIl2Cpp]
|
||||
LoadableAsset<Sprite> Icon => MiraAssets.Empty;
|
||||
|
||||
ConfigDefinition NumConfigDefinition => new("Roles", $"Num{RoleName}");
|
||||
/// <summary>
|
||||
/// Gets the BepInEx ConfigDefinition for the amount of players that can have this role.
|
||||
/// </summary>
|
||||
ConfigDefinition NumConfigDefinition => new("Roles", $"Num {GetType().FullName}");
|
||||
|
||||
ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance{RoleName}");
|
||||
/// <summary>
|
||||
/// Gets the BepInEx ConfigDefinition for the chance of this role being selected.
|
||||
/// </summary>
|
||||
ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance {GetType().FullName}");
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role is affected by light sabotages.
|
||||
/// </summary>
|
||||
bool AffectedByLight => Team == ModdedRoleTeams.Crewmate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role can be killed by others.
|
||||
/// </summary>
|
||||
bool CanGetKilled => Team == ModdedRoleTeams.Crewmate;
|
||||
|
||||
bool IsNeutral => Team == ModdedRoleTeams.Neutral;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role can kill others.
|
||||
/// </summary>
|
||||
bool CanKill => Team == ModdedRoleTeams.Impostor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role can use vents.
|
||||
/// </summary>
|
||||
bool CanUseVent => Team == ModdedRoleTeams.Impostor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role's tasks count towards task progress.
|
||||
/// </summary>
|
||||
bool TasksCount => Team == ModdedRoleTeams.Crewmate;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role is a Ghost.
|
||||
/// </summary>
|
||||
bool IsGhostRole => false;
|
||||
|
||||
bool CreateCustomTab => true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the role should show up in the Role Options menu.
|
||||
/// </summary>
|
||||
bool HideSettings => IsGhostRole;
|
||||
|
||||
RoleTypes GhostRole => Team == ModdedRoleTeams.Crewmate ? RoleTypes.CrewmateGhost : RoleTypes.ImpostorGhost;
|
||||
/// <summary>
|
||||
/// Gets the role hint style. See <see cref="RoleHintType"/> enum for all options.
|
||||
/// </summary>
|
||||
RoleHintType RoleHintType => RoleHintType.RoleTab;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Ghost role that is applied when the player is killed.
|
||||
/// </summary>
|
||||
RoleTypes GhostRole => Team == ModdedRoleTeams.Impostor ? RoleTypes.ImpostorGhost : RoleTypes.CrewmateGhost;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the parent mod of this role.
|
||||
/// </summary>
|
||||
MiraPluginInfo ParentMod => CustomRoleManager.FindParentMod(this);
|
||||
|
||||
/// <summary>
|
||||
/// Runs on the PlayerControl FixedUpdate method for any player with this role
|
||||
/// </summary>
|
||||
/// <param name="playerControl">The PlayerControl that has this role</param>
|
||||
void PlayerControlFixedUpdate(PlayerControl playerControl) { }
|
||||
|
||||
/// <summary>
|
||||
/// Only runs for local player on HudManager Update method
|
||||
/// This method runs on the PlayerControl.FixedUpdate method for ALL players with this role.
|
||||
/// </summary>
|
||||
/// <param name="hudManager">Reference to HudManager instance</param>
|
||||
void HudUpdate(HudManager hudManager) { }
|
||||
/// <param name="playerControl">The PlayerControl that has this role.</param>
|
||||
void PlayerControlFixedUpdate(PlayerControl playerControl)
|
||||
{
|
||||
}
|
||||
|
||||
string GetCustomEjectionMessage(NetworkedPlayerInfo player)
|
||||
/// <summary>
|
||||
/// This method runs on the HudManager.Update method ONLY when the LOCAL player has this role.
|
||||
/// </summary>
|
||||
/// <param name="hudManager">Reference to HudManager instance.</param>
|
||||
void HudUpdate(HudManager hudManager)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a custom ejection message for the role. Return null to use the default message.
|
||||
/// </summary>
|
||||
/// <param name="player">The NetworkedPlayerInfo object for this player.</param>
|
||||
/// <returns>A string with a custom ejection message or null.</returns>
|
||||
string? GetCustomEjectionMessage(NetworkedPlayerInfo player)
|
||||
{
|
||||
return Team == ModdedRoleTeams.Impostor ? $"{player.PlayerName} was The {RoleName}" : null;
|
||||
}
|
||||
|
||||
StringBuilder SetTabText()
|
||||
{
|
||||
var taskStringBuilder = Helpers.CreateForRole(this);
|
||||
return taskStringBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the custom Role Tab text for this role.
|
||||
/// </summary>
|
||||
/// <returns>A StringBuilder with the role tab text.</returns>
|
||||
StringBuilder SetTabText() => Helpers.CreateForRole(this);
|
||||
|
||||
/// <summary>
|
||||
/// Determine whether a given modifier can be applied to this role.
|
||||
/// </summary>
|
||||
/// <param name="modifier">The modifier to be tested.</param>
|
||||
/// <returns>True if the modifier is valid on this role, false otherwise.</returns>
|
||||
bool IsModifierApplicable(BaseModifier modifier)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
NetData GetNetData()
|
||||
{
|
||||
ParentMod.PluginConfig.TryGetEntry<int>(NumConfigDefinition, out var numEntry);
|
||||
ParentMod.PluginConfig.TryGetEntry<int>(ChanceConfigDefinition, out var chanceEntry);
|
||||
|
||||
return new NetData(RoleId.Get(GetType()), BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// Copyright (c) PlaceholderCompany. All rights reserved.
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// The type of hint style for a role to use.
|
||||
/// </summary>
|
||||
public enum RoleHintType
|
||||
{
|
||||
/// <summary>
|
||||
/// No hint.
|
||||
/// </summary>
|
||||
None,
|
||||
|
||||
/// <summary>
|
||||
/// Original game style (hint above tasks).
|
||||
/// </summary>
|
||||
TaskHint,
|
||||
|
||||
/// <summary>
|
||||
/// Use custom role tab.
|
||||
/// </summary>
|
||||
RoleTab,
|
||||
}
|
||||
@@ -13,10 +13,23 @@ namespace MiraAPI.Utilities.Assets;
|
||||
/// </summary>
|
||||
public static class SpriteTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Delegate for LoadImage.
|
||||
/// </summary>
|
||||
public delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
|
||||
|
||||
public static DLoadImage ICallLoadImage;
|
||||
/// <summary>
|
||||
/// The ICall for LoadImage.
|
||||
/// </summary>
|
||||
public static DLoadImage? ICallLoadImage;
|
||||
|
||||
/// <summary>
|
||||
/// Load an image into a texture.
|
||||
/// </summary>
|
||||
/// <param name="tex">The texture.</param>
|
||||
/// <param name="data">Byte data of image.</param>
|
||||
/// <param name="markNonReadable">Mark nonreadable.</param>
|
||||
/// <returns>True if succeeded.</returns>
|
||||
public static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
ICallLoadImage ??= IL2CPP.ResolveICall<DLoadImage>("UnityEngine.ImageConversion::LoadImage");
|
||||
@@ -48,4 +61,4 @@ public static class SpriteTools
|
||||
|
||||
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
+222
-80
@@ -1,81 +1,149 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using Reactor.Utilities;
|
||||
using System.Linq;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for various classes.
|
||||
/// </summary>
|
||||
public static class Extensions
|
||||
{
|
||||
internal static NetData GetNetData(this ICustomRole role)
|
||||
{
|
||||
role.ParentMod.PluginConfig.TryGetEntry<int>(role.NumConfigDefinition, out var numEntry);
|
||||
role.ParentMod.PluginConfig.TryGetEntry<int>(role.ChanceConfigDefinition, out var chanceEntry);
|
||||
|
||||
return new NetData(RoleId.Get(role.GetType()), BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a type is static.
|
||||
/// </summary>
|
||||
/// <param name="type">The type being checked.</param>
|
||||
/// <returns>True if the type is static, false otherwise.</returns>
|
||||
public static bool IsStatic(this Type type)
|
||||
{
|
||||
return type is { IsClass: true, IsAbstract: true, IsSealed: true };
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets a darkened version of a color.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="darknessAmount">A darkness amount between 0 and 255.</param>
|
||||
/// <returns>The darkened color.</returns>
|
||||
public static Color32 GetShadowColor(this Color32 color, byte darknessAmount)
|
||||
{
|
||||
return
|
||||
new Color32((byte)Mathf.Clamp(color.r - darknessAmount, 0, 255), (byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.b - darknessAmount, 0, 255), byte.MaxValue);
|
||||
new Color32(
|
||||
(byte)Mathf.Clamp(color.r - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.g - darknessAmount, 0, 255),
|
||||
(byte)Mathf.Clamp(color.b - darknessAmount, 0, 255),
|
||||
byte.MaxValue);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Truncates a string to a specified length.
|
||||
/// </summary>
|
||||
/// <param name="value">The original string.</param>
|
||||
/// <param name="maxLength">The maximum length.</param>
|
||||
/// <param name="truncationSuffix">An option suffix to attach at the end of the truncated string.</param>
|
||||
/// <returns>A truncated string of maxLength with the attached suffix.</returns>
|
||||
public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…")
|
||||
{
|
||||
return value?.Length > maxLength
|
||||
? value[..maxLength] + truncationSuffix
|
||||
: value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Chunks a collection of NetData into smaller arrays.
|
||||
/// </summary>
|
||||
/// <param name="dataCollection">A collection of NetData objects.</param>
|
||||
/// <param name="chunkSize">The max chunk size in bytes.</param>
|
||||
/// <returns>A Queue of NetData arrays.</returns>
|
||||
public static Queue<NetData[]> ChunkNetData(this IEnumerable<NetData> dataCollection, int chunkSize)
|
||||
{
|
||||
Queue<NetData[]> chunks = [];
|
||||
List<NetData> current = [];
|
||||
|
||||
|
||||
var count = 0;
|
||||
foreach (var netData in dataCollection)
|
||||
{
|
||||
var length = netData.GetLength();
|
||||
|
||||
|
||||
if (length > chunkSize)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"NetData length is greater than chunk size: {length} > {chunkSize}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
if (count + length > chunkSize)
|
||||
{
|
||||
chunks.Enqueue(current.ToArray());
|
||||
current.Clear();
|
||||
count = 0;
|
||||
}
|
||||
|
||||
|
||||
current.Add(netData);
|
||||
}
|
||||
|
||||
|
||||
if (current.Count > 0)
|
||||
{
|
||||
chunks.Enqueue(current.ToArray());
|
||||
chunks.Enqueue([.. current]);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a given OptionBehaviour is for a custom option.
|
||||
/// </summary>
|
||||
/// <param name="optionBehaviour">The OptionBehaviour to be tested.</param>
|
||||
/// <returns>True if the OptionBehaviour is for a custom options, false otherwise.</returns>
|
||||
public static bool IsCustom(this OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour));
|
||||
return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour == optionBehaviour);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<PlayerControl, ModifierComponent> ModifierComponents = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the ModifierComponent for a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl object.</param>
|
||||
/// <returns>A ModifierComponent if there is one, null otherwise.</returns>
|
||||
public static ModifierComponent? GetModifierComponent(this PlayerControl player)
|
||||
{
|
||||
return player.GetComponent<ModifierComponent>();
|
||||
if (ModifierComponents.TryGetValue(player, out var component))
|
||||
{
|
||||
return component;
|
||||
}
|
||||
|
||||
component = player.GetComponent<ModifierComponent>();
|
||||
if (component)
|
||||
{
|
||||
ModifierComponents[player] = component;
|
||||
}
|
||||
|
||||
return component;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Randomizes a list.
|
||||
/// </summary>
|
||||
/// <param name="list">The list object.</param>
|
||||
/// <typeparam name="T">The type of object the list contains.</typeparam>
|
||||
/// <returns>A randomized list made from the original list.</returns>
|
||||
public static List<T> Randomize<T>(this List<T> list)
|
||||
{
|
||||
List<T> randomizedList = [];
|
||||
@@ -89,110 +157,184 @@ public static class Extensions
|
||||
return randomizedList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a modifier by its type, or null if the player doesn't have it.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl object.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>The Modifier if it is found, null otherwise.</returns>
|
||||
public static T? GetModifier<T>(this PlayerControl? player) where T : BaseModifier
|
||||
{
|
||||
return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has a modifier.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl object.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
public static bool HasModifier<T>(this PlayerControl? player) where T : BaseModifier
|
||||
{
|
||||
return player?.GetModifierComponent() != null && player.GetModifierComponent()!.ActiveModifiers.Exists(x => x is T);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a player has a modifier by its ID.
|
||||
/// </summary>
|
||||
/// <param name="player">The PlayerControl object.</param>
|
||||
/// <param name="id">The Modifier ID.</param>
|
||||
/// <returns>True if the Modifier is present, false otherwise.</returns>
|
||||
public static bool HasModifier(this PlayerControl? player, uint id)
|
||||
{
|
||||
return player?.GetModifierComponent() != null && player.GetModifierComponent()!.ActiveModifiers.Exists(x => x.ModifierId == id);
|
||||
}
|
||||
|
||||
public static void AddModifier<T>(this PlayerControl player) where T : BaseModifier
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to remove a modifier from a player.
|
||||
/// </summary>
|
||||
/// <param name="target">The player to remove the modifier from.</param>
|
||||
/// <param name="modifierId">The ID of the modifier.</param>
|
||||
[MethodRpc((uint)MiraRpc.RemoveModifier)]
|
||||
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
|
||||
{
|
||||
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
|
||||
target.GetModifierComponent()?.RemoveModifier(modifierId);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to remove a modifier from a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to remove the modifier from.</param>
|
||||
/// <typeparam name="T">The Type of the Modifier.</typeparam>
|
||||
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
|
||||
{
|
||||
var id = ModifierManager.GetModifierId(typeof(T));
|
||||
|
||||
if (id == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierComponent.RpcAddModifier(player, id);
|
||||
player.RpcRemoveModifier(id.Value);
|
||||
}
|
||||
|
||||
public static void RemoveModifier<T>(this PlayerControl player) where T : BaseModifier
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to add a modifier to a player.
|
||||
/// </summary>
|
||||
/// <param name="target">The player to add the modifier to.</param>
|
||||
/// <param name="modifierId">The modifier ID.</param>
|
||||
[MethodRpc((uint)MiraRpc.AddModifier)]
|
||||
public static void RpcAddModifier(this PlayerControl target, uint modifierId)
|
||||
{
|
||||
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
|
||||
var type = ModifierManager.GetModifierType(modifierId);
|
||||
if (type == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
|
||||
return;
|
||||
}
|
||||
target.GetModifierComponent()?.AddModifier(type);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Remote Procedure Call to add a modifier to a player.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to add the modifier to.</param>
|
||||
/// <typeparam name="T">The modifier Type.</typeparam>
|
||||
public static void RpcAddModifier<T>(this PlayerControl player) where T : BaseModifier
|
||||
{
|
||||
var id = ModifierManager.GetModifierId(typeof(T));
|
||||
if (id == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
ModifierComponent.RpcRemoveModifier(player, id);
|
||||
player.RpcAddModifier(id.Value);
|
||||
}
|
||||
|
||||
public static Color DarkenColor(this Color color)
|
||||
/// <summary>
|
||||
/// Darkens a color by a specified amount.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">A float amount between 0 and 1.</param>
|
||||
/// <returns>The darkened color.</returns>
|
||||
public static Color DarkenColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r - 0.45f, color.g - 0.45f, color.b - 0.45f);
|
||||
}
|
||||
public static Color GetAlternateColor(this Color color)
|
||||
{
|
||||
return color.IsColorDark() ? LightenColor(color) : DarkenColor(color);
|
||||
return new Color(color.r - amount, color.g - amount, color.b - amount);
|
||||
}
|
||||
|
||||
public static Color LightenColor(this Color color)
|
||||
/// <summary>
|
||||
/// Gets an alternate color based on the original color.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">The amount to darken or lighten the original color by between 0.0 and 1.0.</param>
|
||||
/// <returns>An alternate color that has been darkened or lightened.</returns>
|
||||
public static Color GetAlternateColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r + 0.45f, color.g + 0.45f, color.b + 0.45f);
|
||||
return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Lightens a color by a specified amount.
|
||||
/// </summary>
|
||||
/// <param name="color">The original color.</param>
|
||||
/// <param name="amount">A float amount between 0.0 and 1.0.</param>
|
||||
/// <returns>The lightened color.</returns>
|
||||
public static Color LightenColor(this Color color, float amount = 0.45f)
|
||||
{
|
||||
return new Color(color.r + amount, color.g + amount, color.b + amount);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a color is dark.
|
||||
/// </summary>
|
||||
/// <param name="color">The color to check.</param>
|
||||
/// <returns>True if the color is dark, false otherwise.</returns>
|
||||
public static bool IsColorDark(this Color color)
|
||||
{
|
||||
return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f };
|
||||
}
|
||||
|
||||
public static DeadBody? NearestDeadBody(this PlayerControl playerControl, float radius)
|
||||
/// <summary>
|
||||
/// Gets the nearest dead body to a player.
|
||||
/// </summary>
|
||||
/// <param name="playerControl">The player object.</param>
|
||||
/// <param name="radius">The radius to search within.</param>
|
||||
/// <returns>The dead body if it is found, or null there is none within the radius.</returns>
|
||||
public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius)
|
||||
{
|
||||
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
|
||||
Physics2D.OverlapCircle(playerControl.GetTruePosition(), radius, Helpers.Filter, results);
|
||||
return results.ToArray()
|
||||
.Where(collider2D => collider2D.CompareTag("DeadBody"))
|
||||
.Select(collider2D => collider2D.GetComponent<DeadBody>())
|
||||
.FirstOrDefault(component => component && !component.Reported);
|
||||
return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius).Find(component => component && !component.Reported);
|
||||
}
|
||||
|
||||
public static T? GetNearestObjectOfType<T>(this PlayerControl playerControl, float radius, string? colliderTag=null, Func<T, bool>? predicate=null) where T : Component
|
||||
/// <summary>
|
||||
/// Finds the nearest object of a specified type to a player. It will only work if the object has a collider.
|
||||
/// </summary>
|
||||
/// <param name="playerControl">The player object.</param>
|
||||
/// <param name="radius">The radius to search within.</param>
|
||||
/// <param name="colliderTag">An optional collider tag.</param>
|
||||
/// <param name="predicate">Optional predicate to test if the object is valid.</param>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <returns>The object if it was found, or null if there is none within the radius.</returns>
|
||||
public static T? GetNearestObjectOfType<T>(this PlayerControl playerControl, float radius, string? colliderTag = null, Predicate<T>? predicate = null) where T : Component
|
||||
{
|
||||
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
|
||||
Physics2D.OverlapCircle(playerControl.GetTruePosition(), radius, Helpers.Filter, results);
|
||||
return results.ToArray()
|
||||
.Where(collider2D => colliderTag == null || collider2D.CompareTag(colliderTag))
|
||||
.Select(collider2D => collider2D.GetComponent<T>())
|
||||
.FirstOrDefault(predicate ?? (component => component));
|
||||
return Helpers.GetNearestObjectsOfType<T>(playerControl.GetTruePosition(), radius, colliderTag).Find(predicate ?? (component => component));
|
||||
}
|
||||
|
||||
public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance)
|
||||
/// <summary>
|
||||
/// Gets the nearest player to a player.
|
||||
/// </summary>
|
||||
/// <param name="playerControl">The player object.</param>
|
||||
/// <param name="includeImpostors">Whether impostors should be included in the search.</param>
|
||||
/// <param name="distance">The radius to search within.</param>
|
||||
/// <param name="ignoreColliders">Whether colliders should be ignored when searching.</param>
|
||||
/// <returns>The closest player if there is one, false otherwise.</returns>
|
||||
public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false)
|
||||
{
|
||||
PlayerControl? result = null;
|
||||
if (!ShipStatus.Instance)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders)
|
||||
.Where(playerInfo => !playerInfo.Data.Disconnected && playerInfo.PlayerId != playerControl.PlayerId && !playerInfo.Data.IsDead &&
|
||||
(includeImpostors || !playerInfo.Data.Role.IsImpostor));
|
||||
|
||||
var truePosition = playerControl.GetTruePosition();
|
||||
|
||||
var filteredPlayers = GameData.Instance.AllPlayers.ToArray()
|
||||
.Where(playerInfo => !playerInfo.Disconnected && playerInfo.PlayerId != playerControl.PlayerId && !playerInfo.IsDead &&
|
||||
(includeImpostors || !playerInfo.Role.IsImpostor));
|
||||
|
||||
foreach (var playerInfo in filteredPlayers)
|
||||
{
|
||||
var @object = playerInfo.Object;
|
||||
if (!@object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var vector = @object.GetTruePosition() - truePosition;
|
||||
var magnitude = vector.magnitude;
|
||||
if (!(magnitude <= distance) || PhysicsHelpers.AnyNonTriggersBetween(truePosition,
|
||||
vector.normalized,
|
||||
magnitude, LayerMask.GetMask("Ship", "Objects")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = @object;
|
||||
distance = magnitude;
|
||||
}
|
||||
return result;
|
||||
return filteredPlayers.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
+200
-12
@@ -1,30 +1,203 @@
|
||||
using MiraAPI.Roles;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// A class that contains helper methods.
|
||||
/// </summary>
|
||||
public static class Helpers
|
||||
{
|
||||
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue);
|
||||
public static PlainShipRoom GetRoom(Vector3 pos)
|
||||
/// <summary>
|
||||
/// NotShipMask filter for collisions.
|
||||
/// </summary>
|
||||
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(
|
||||
Constants.NotShipMask,
|
||||
float.MinValue,
|
||||
float.MaxValue);
|
||||
|
||||
/// <summary>
|
||||
/// Get the room at a specific position.
|
||||
/// </summary>
|
||||
/// <param name="pos">The position.</param>
|
||||
/// <returns>The ship room if its found.</returns>
|
||||
public static PlainShipRoom? GetRoom(Vector3 pos)
|
||||
{
|
||||
return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos));
|
||||
}
|
||||
|
||||
public static TextMeshPro CreateTextLabel(string name, Transform parent,
|
||||
AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f,
|
||||
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
|
||||
/// <summary>
|
||||
/// Gets a list of dead bodies within a radius.
|
||||
/// </summary>
|
||||
/// <param name="source">The source location.</param>
|
||||
/// <param name="radius">The radius to search in.</param>
|
||||
/// <returns>A list of dead bodies.</returns>
|
||||
public static List<DeadBody> GetNearestDeadBodies(Vector2 source, float radius)
|
||||
{
|
||||
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
|
||||
Physics2D.OverlapCircle(source, radius, Filter, results);
|
||||
return results.ToArray()
|
||||
.Where(collider2D => collider2D.CompareTag("DeadBody"))
|
||||
.Select(collider2D => collider2D.GetComponent<DeadBody>()).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a list of objects within a radius.
|
||||
/// </summary>
|
||||
/// <param name="source">The source point.</param>
|
||||
/// <param name="radius">The radius to search in.</param>
|
||||
/// <param name="colliderTag">An optional collider tag.</param>
|
||||
/// <typeparam name="T">The type of the object.</typeparam>
|
||||
/// <returns>A list of objects of type T.</returns>
|
||||
public static List<T> GetNearestObjectsOfType<T>(Vector2 source, float radius, string? colliderTag = null)
|
||||
where T : Component
|
||||
{
|
||||
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
|
||||
Physics2D.OverlapCircle(source, radius, Filter, results);
|
||||
return results.ToArray()
|
||||
.Where(collider2D => colliderTag == null || collider2D.CompareTag(colliderTag))
|
||||
.Select(collider2D => collider2D.GetComponent<T>()).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest players to a specific point.
|
||||
/// </summary>
|
||||
/// <param name="source">The source point.</param>
|
||||
/// <param name="radius">The radius to search in.</param>
|
||||
/// <param name="ignoreColliders">Whether colliders should be ignored.</param>
|
||||
/// <returns>A list of Player Controls in the radius.</returns>
|
||||
public static List<PlayerControl> GetClosestPlayersInCircle(
|
||||
Vector2 source,
|
||||
float radius,
|
||||
bool ignoreColliders = true)
|
||||
{
|
||||
var newList = GetNearestObjectsOfType<PlayerControl>(source, radius);
|
||||
|
||||
if (!ignoreColliders)
|
||||
{
|
||||
return newList;
|
||||
}
|
||||
|
||||
return (from player in newList
|
||||
let vector = player.GetTruePosition() - source
|
||||
let magnitude = vector.magnitude
|
||||
where !PhysicsHelpers.AnyNonTriggersBetween(
|
||||
source,
|
||||
vector.normalized,
|
||||
magnitude,
|
||||
Constants.ShipAndObjectsMask)
|
||||
select player).ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest players to a specific player.
|
||||
/// </summary>
|
||||
/// <param name="source">The source player.</param>
|
||||
/// <param name="distance">Distance to search in.</param>
|
||||
/// <param name="ignoreColliders">Whether to ignore colliders.</param>
|
||||
/// <param name="ignoreSource">Whether to ignore the source player.</param>
|
||||
/// <returns>A list of PlayerControls.</returns>
|
||||
public static List<PlayerControl> GetClosestPlayers(
|
||||
PlayerControl source,
|
||||
float distance = 2f,
|
||||
bool ignoreColliders = true,
|
||||
bool ignoreSource = true)
|
||||
{
|
||||
if (!ShipStatus.Instance)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
var myPos = source.GetTruePosition();
|
||||
var players = GetClosestPlayers(myPos, distance, ignoreColliders);
|
||||
|
||||
return ignoreSource ? players.Where(plr => plr.PlayerId != source.PlayerId).ToList() : players;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the closest players to a specific point.
|
||||
/// </summary>
|
||||
/// <param name="source">The source point.</param>
|
||||
/// <param name="distance">The distance to search in.</param>
|
||||
/// <param name="ignoreColliders">Whether to ignore colliders.</param>
|
||||
/// <returns>A list of Player Controls.</returns>
|
||||
public static List<PlayerControl> GetClosestPlayers(
|
||||
Vector2 source,
|
||||
float distance = 2f,
|
||||
bool ignoreColliders = true)
|
||||
{
|
||||
if (!ShipStatus.Instance)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
List<PlayerControl> outputList = [];
|
||||
outputList.Clear();
|
||||
var allPlayers = GameData.Instance.AllPlayers.ToArray().Select(x => x.Object);
|
||||
|
||||
outputList.AddRange(
|
||||
from playerControl in allPlayers
|
||||
where playerControl && playerControl.Collider.enabled
|
||||
let vector = playerControl.GetTruePosition() - source
|
||||
let magnitude = vector.magnitude
|
||||
where magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(
|
||||
source,
|
||||
vector.normalized,
|
||||
magnitude,
|
||||
Constants.ShipAndObjectsMask))
|
||||
select playerControl);
|
||||
|
||||
outputList.Sort(
|
||||
delegate(PlayerControl a, PlayerControl b)
|
||||
{
|
||||
var magnitude2 = (a.GetTruePosition() - source).magnitude;
|
||||
var magnitude3 = (b.GetTruePosition() - source).magnitude;
|
||||
if (magnitude2 > magnitude3)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (magnitude2 < magnitude3)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
return outputList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a TextMeshPro object with the specified parameters.
|
||||
/// </summary>
|
||||
/// <param name="name">The name of the object.</param>
|
||||
/// <param name="parent">The object parent.</param>
|
||||
/// <param name="alignment">The alignment of the TMP object.</param>
|
||||
/// <param name="distance">The distance from the edge.</param>
|
||||
/// <param name="fontSize">The font size.</param>
|
||||
/// <param name="textAlignment">The text alignment.</param>
|
||||
/// <returns>A new TMP object.</returns>
|
||||
public static TextMeshPro CreateTextLabel(
|
||||
string name,
|
||||
Transform parent,
|
||||
AspectPosition.EdgeAlignments alignment,
|
||||
Vector3 distance,
|
||||
float fontSize = 2f,
|
||||
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
|
||||
{
|
||||
var textObj = new GameObject(name)
|
||||
{
|
||||
transform =
|
||||
{
|
||||
parent = parent
|
||||
parent = parent,
|
||||
},
|
||||
layer = LayerMask.NameToLayer("UI")
|
||||
layer = LayerMask.NameToLayer("UI"),
|
||||
};
|
||||
|
||||
var textMeshPro = textObj.AddComponent<TextMeshPro>();
|
||||
@@ -41,11 +214,21 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
|
||||
return textMeshPro;
|
||||
}
|
||||
|
||||
public static DeadBody GetBodyById(byte id)
|
||||
/// <summary>
|
||||
/// Gets a DeadBody by its parent ID.
|
||||
/// </summary>
|
||||
/// <param name="id">The player ID.</param>
|
||||
/// <returns>A dead body or null if its not found.</returns>
|
||||
public static DeadBody? GetBodyById(byte id)
|
||||
{
|
||||
return Object.FindObjectsOfType<DeadBody>().FirstOrDefault(body => body.ParentId == id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the suffix for a MiraNumberSuffixes enum.
|
||||
/// </summary>
|
||||
/// <param name="suffix">The MiraNumberSuffixes enum.</param>
|
||||
/// <returns>A suffix based on the enum.</returns>
|
||||
public static string GetSuffix(MiraNumberSuffixes suffix)
|
||||
{
|
||||
return suffix switch
|
||||
@@ -54,16 +237,21 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
|
||||
MiraNumberSuffixes.Multiplier => "x",
|
||||
MiraNumberSuffixes.Seconds => "s",
|
||||
MiraNumberSuffixes.Percent => "%",
|
||||
_ => string.Empty
|
||||
_ => string.Empty,
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a string builder for the Role Tab.
|
||||
/// </summary>
|
||||
/// <param name="role">The ICustomRole object.</param>
|
||||
/// <returns>A StringBuilder.</returns>
|
||||
public static StringBuilder CreateForRole(ICustomRole role)
|
||||
{
|
||||
var taskStringBuilder = new StringBuilder();
|
||||
taskStringBuilder.AppendLine($"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
|
||||
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
|
||||
taskStringBuilder.Append("<size=70%>");
|
||||
taskStringBuilder.AppendLine($"{role.RoleLongDescription}");
|
||||
taskStringBuilder.AppendLine(CultureInfo.InvariantCulture, $"{role.RoleLongDescription}");
|
||||
return taskStringBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,7 @@ public float SussyLevel { get; set; } = 4f; // You can set a default value here.
|
||||
|
||||
Here are the available Option Attributes and their signatures:
|
||||
```csharp
|
||||
ModdedEnumOption(string name, Type enumType, Type roleType = null)
|
||||
ModdedEnumOption(string name, Type enumType, string[]? values = null, Type? roleType = null)
|
||||
|
||||
ModdedNumberOption(
|
||||
string name,
|
||||
@@ -127,11 +127,9 @@ ModdedNumberOption(
|
||||
float increment=1
|
||||
NumberSuffixes suffixType = NumberSuffixes.None,
|
||||
bool zeroInfinity = false,
|
||||
Type roleType = null)
|
||||
|
||||
ModdedStringOption(string title, string[] values, Type roleType = null)
|
||||
Type? roleType = null)
|
||||
|
||||
ModdedToggleOption(string name, Type roleType = null)
|
||||
ModdedToggleOption(string name, Type? roleType = null)
|
||||
```
|
||||
|
||||
### ModdedOption Properties
|
||||
@@ -144,7 +142,6 @@ public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezu
|
||||
Here is a full list of ModdedOption classes you can use:
|
||||
- `ModdedEnumOption`
|
||||
- `ModdedNumberOption`
|
||||
- `ModdedStringOption`
|
||||
- `ModdedToggleOption`
|
||||
|
||||
To see a full example of an options class, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
|
||||
@@ -180,6 +177,15 @@ public class MyOptionsGroup : AbstractOptionGroup
|
||||
|
||||
An example can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/Roles/CustomRoleSettings.cs).
|
||||
|
||||
## Custom Murders
|
||||
Mira API provides it's own implementation for murders. Our implementation allows for more customization on kills, and helps bypass server checks.
|
||||
You can use `PlayerControl.RpcCustomMurder` to perform a networked custom murder, or `PlayerControl.CustomMurder` to normally perform a custom murder.
|
||||
For example:
|
||||
```cs
|
||||
PlayerControl.LocalPlayer.RpcCustomMurder(Target, createDeadBody: false, teleportMurderer: false, playKillSound: false, resetKillTimer: false, showKillAnim: false);
|
||||
```
|
||||
This will kill a player without creating a dead body and without teleporting the murderer.
|
||||
|
||||
## Buttons
|
||||
|
||||
Mira API provides a simple interface for adding ability buttons to the game. There is only 2 steps:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
|
||||
"settings": {
|
||||
"orderingRules": {
|
||||
"usingDirectivesPlacement": "outsideNamespace"
|
||||
},
|
||||
"layoutRules": {
|
||||
"newlineAtEndOfFile": "require",
|
||||
"allowDoWhileOnClosingBrace": true
|
||||
},
|
||||
"documentationRules": {
|
||||
"xmlHeader": false,
|
||||
"documentInterfaces": false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
<?xml version="1.0"?>
|
||||
<RuleSet Name="StyleCop.Analyzers rules with default action"
|
||||
Description="StyleCop.Analyzers with default action. Rules with IsEnabledByDefault = false are disabled."
|
||||
ToolsVersion="14.0">
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.SpecialRules">
|
||||
<Rule Id="SA0001" Action="None" /> <!-- XML comment analysis disabled -->
|
||||
<Rule Id="SA0002" Action="Warning" /> <!-- Invalid settings file -->
|
||||
</Rules>
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.SpacingRules">
|
||||
<Rule Id="SA1000" Action="Warning" /> <!-- Keywords should be spaced correctly -->
|
||||
<Rule Id="SA1001" Action="Warning" /> <!-- Commas should be spaced correctly -->
|
||||
<Rule Id="SA1002" Action="Warning" /> <!-- Semicolons should be spaced correctly -->
|
||||
<Rule Id="SA1003" Action="None" /> <!-- Symbols should be spaced correctly -->
|
||||
<Rule Id="SA1004" Action="Warning" /> <!-- Documentation lines should begin with single space -->
|
||||
<Rule Id="SA1005" Action="Warning" /> <!-- Single line comments should begin with single space -->
|
||||
<Rule Id="SA1006" Action="Warning" /> <!-- Preprocessor keywords should not be preceded by space -->
|
||||
<Rule Id="SA1007" Action="Warning" /> <!-- Operator keyword should be followed by space -->
|
||||
<Rule Id="SA1008" Action="None" /> <!-- Opening parenthesis should be spaced correctly -->
|
||||
<Rule Id="SA1009" Action="None" /> <!-- Closing parenthesis should be spaced correctly -->
|
||||
<Rule Id="SA1010" Action="Warning" /> <!-- Opening square brackets should be spaced correctly -->
|
||||
<Rule Id="SA1011" Action="Warning" /> <!-- Closing square brackets should be spaced correctly -->
|
||||
<Rule Id="SA1012" Action="Warning" /> <!-- Opening braces should be spaced correctly -->
|
||||
<Rule Id="SA1013" Action="Warning" /> <!-- Closing braces should be spaced correctly -->
|
||||
<Rule Id="SA1014" Action="Warning" /> <!-- Opening generic brackets should be spaced correctly -->
|
||||
<Rule Id="SA1015" Action="Warning" /> <!-- Closing generic brackets should be spaced correctly -->
|
||||
<Rule Id="SA1016" Action="Warning" /> <!-- Opening attribute brackets should be spaced correctly -->
|
||||
<Rule Id="SA1017" Action="Warning" /> <!-- Closing attribute brackets should be spaced correctly -->
|
||||
<Rule Id="SA1018" Action="Warning" /> <!-- Nullable type symbols should be spaced correctly -->
|
||||
<Rule Id="SA1019" Action="Warning" /> <!-- Member access symbols should be spaced correctly -->
|
||||
<Rule Id="SA1020" Action="Warning" /> <!-- Increment decrement symbols should be spaced correctly -->
|
||||
<Rule Id="SA1021" Action="Warning" /> <!-- Negative signs should be spaced correctly -->
|
||||
<Rule Id="SA1022" Action="Warning" /> <!-- Positive signs should be spaced correctly -->
|
||||
<Rule Id="SA1023" Action="Warning" /> <!-- Dereference and access of symbols should be spaced correctly -->
|
||||
<Rule Id="SA1024" Action="Warning" /> <!-- Colons should be spaced correctly -->
|
||||
<Rule Id="SA1025" Action="Warning" /> <!-- Code should not contain multiple whitespace in a row -->
|
||||
<Rule Id="SA1026" Action="Warning" /> <!-- Code should not contain space after new or stackalloc keyword in implicitly typed array allocation -->
|
||||
<Rule Id="SA1027" Action="Warning" /> <!-- Use tabs correctly -->
|
||||
<Rule Id="SA1028" Action="Warning" /> <!-- Code should not contain trailing whitespace -->
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.ReadabilityRules">
|
||||
<Rule Id="SA1100" Action="Warning" /> <!-- Do not prefix calls with base unless local implementation exists -->
|
||||
<Rule Id="SA1101" Action="None" /> <!-- Prefix local calls with this -->
|
||||
<Rule Id="SA1102" Action="Warning" /> <!-- Query clause should follow previous clause -->
|
||||
<Rule Id="SA1103" Action="Warning" /> <!-- Query clauses should be on separate lines or all on one line -->
|
||||
<Rule Id="SA1104" Action="Warning" /> <!-- Query clause should begin on new line when previous clause spans multiple lines -->
|
||||
<Rule Id="SA1105" Action="Warning" /> <!-- Query clauses spanning multiple lines should begin on own line -->
|
||||
<Rule Id="SA1106" Action="Warning" /> <!-- Code should not contain empty statements -->
|
||||
<Rule Id="SA1107" Action="Warning" /> <!-- Code should not contain multiple statements on one line -->
|
||||
<Rule Id="SA1108" Action="Warning" /> <!-- Block statements should not contain embedded comments -->
|
||||
<Rule Id="SA1109" Action="None" /> <!-- Block statements should not contain embedded regions -->
|
||||
<Rule Id="SA1110" Action="Warning" /> <!-- Opening parenthesis or bracket should be on declaration line -->
|
||||
<Rule Id="SA1111" Action="None" /> <!-- Closing parenthesis should be on line of last parameter -->
|
||||
<Rule Id="SA1112" Action="Warning" /> <!-- Closing parenthesis should be on line of opening parenthesis -->
|
||||
<Rule Id="SA1113" Action="Warning" /> <!-- Comma should be on the same line as previous parameter -->
|
||||
<Rule Id="SA1114" Action="Warning" /> <!-- Parameter list should follow declaration -->
|
||||
<Rule Id="SA1115" Action="Warning" /> <!-- Parameter should follow comma -->
|
||||
<Rule Id="SA1116" Action="Warning" /> <!-- Split parameters should start on line after declaration -->
|
||||
<Rule Id="SA1117" Action="Warning" /> <!-- Parameters should be on same line or separate lines -->
|
||||
<Rule Id="SA1118" Action="None" /> <!-- Parameter should not span multiple lines -->
|
||||
<Rule Id="SA1120" Action="Warning" /> <!-- Comments should contain text -->
|
||||
<Rule Id="SA1121" Action="Warning" /> <!-- Use built-in type alias -->
|
||||
<Rule Id="SA1122" Action="Warning" /> <!-- Use string.Empty for empty strings -->
|
||||
<Rule Id="SA1123" Action="Warning" /> <!-- Do not place regions within elements -->
|
||||
<Rule Id="SA1124" Action="Warning" /> <!-- Do not use regions -->
|
||||
<Rule Id="SA1125" Action="Warning" /> <!-- Use shorthand for nullable types -->
|
||||
<Rule Id="SA1126" Action="None" /> <!-- Prefix calls correctly -->
|
||||
<Rule Id="SA1127" Action="None" /> <!-- Generic type constraints should be on their own line -->
|
||||
<Rule Id="SA1128" Action="None" /> <!-- Put constructor initializers on their own line -->
|
||||
<Rule Id="SA1129" Action="Warning" /> <!-- Do not use default value type constructor -->
|
||||
<Rule Id="SA1130" Action="Warning" /> <!-- Use lambda syntax -->
|
||||
<Rule Id="SA1131" Action="Warning" /> <!-- Use readable conditions -->
|
||||
<Rule Id="SA1132" Action="None" /> <!-- Do not combine fields -->
|
||||
<Rule Id="SA1133" Action="None" /> <!-- Do not combine attributes -->
|
||||
<Rule Id="SA1134" Action="Warning" /> <!-- Attributes should not share line -->
|
||||
<Rule Id="SA1135" Action="Warning" /> <!-- Using directives should be qualified -->
|
||||
<Rule Id="SA1136" Action="Warning" /> <!-- Enum values should be on separate lines -->
|
||||
<Rule Id="SA1137" Action="Warning" /> <!-- Elements should have the same indentation -->
|
||||
<Rule Id="SA1139" Action="Warning" /> <!-- Use literal suffix notation instead of casting -->
|
||||
<Rule Id="SX1101" Action="None" /> <!-- Do not prefix local calls with 'this.' -->
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.OrderingRules">
|
||||
<Rule Id="SA1200" Action="Warning" /> <!-- Using directives should be placed correctly -->
|
||||
<Rule Id="SA1201" Action="None" /> <!-- Elements should appear in the correct order -->
|
||||
<Rule Id="SA1202" Action="None" /> <!-- Elements should be ordered by access -->
|
||||
<Rule Id="SA1203" Action="None" /> <!-- Constants should appear before fields -->
|
||||
<Rule Id="SA1204" Action="None" /> <!-- Static elements should appear before instance elements -->
|
||||
<Rule Id="SA1205" Action="None" /> <!-- Partial elements should declare access -->
|
||||
<Rule Id="SA1206" Action="Warning" /> <!-- Declaration keywords should follow order -->
|
||||
<Rule Id="SA1207" Action="Warning" /> <!-- Protected should come before internal -->
|
||||
<Rule Id="SA1208" Action="None" /> <!-- System using directives should be placed before other using directives -->
|
||||
<Rule Id="SA1209" Action="Warning" /> <!-- Using alias directives should be placed after other using directives -->
|
||||
<Rule Id="SA1210" Action="Warning" /> <!-- Using directives should be ordered alphabetically by namespace -->
|
||||
<Rule Id="SA1211" Action="Warning" /> <!-- Using alias directives should be ordered alphabetically by alias name -->
|
||||
<Rule Id="SA1212" Action="Warning" /> <!-- Property accessors should follow order -->
|
||||
<Rule Id="SA1213" Action="Warning" /> <!-- Event accessors should follow order -->
|
||||
<Rule Id="SA1214" Action="None" /> <!-- Readonly fields should appear before non-readonly fields -->
|
||||
<Rule Id="SA1216" Action="None" /> <!-- Using static directives should be placed at the correct location -->
|
||||
<Rule Id="SA1217" Action="Warning" /> <!-- Using static directives should be ordered alphabetically -->
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.NamingRules">
|
||||
<Rule Id="SA1300" Action="None" /> <!-- Element should begin with upper-case letter -->
|
||||
<Rule Id="SA1301" Action="None" /> <!-- Element should begin with lower-case letter -->
|
||||
<Rule Id="SA1302" Action="Warning" /> <!-- Interface names should begin with I -->
|
||||
<Rule Id="SA1303" Action="Warning" /> <!-- Const field names should begin with upper-case letter -->
|
||||
<Rule Id="SA1304" Action="Warning" /> <!-- Non-private readonly fields should begin with upper-case letter -->
|
||||
<Rule Id="SA1305" Action="None" /> <!-- Field names should not use Hungarian notation -->
|
||||
<Rule Id="SA1306" Action="Warning" /> <!-- Field names should begin with lower-case letter -->
|
||||
<Rule Id="SA1307" Action="Warning" /> <!-- Accessible fields should begin with upper-case letter -->
|
||||
<Rule Id="SA1308" Action="Warning" /> <!-- Variable names should not be prefixed -->
|
||||
<Rule Id="SA1309" Action="None" /> <!-- Field names should not begin with underscore -->
|
||||
<Rule Id="SA1310" Action="Warning" /> <!-- Field names should not contain underscore -->
|
||||
<Rule Id="SA1311" Action="Warning" /> <!-- Static readonly fields should begin with upper-case letter -->
|
||||
<Rule Id="SA1312" Action="Warning" /> <!-- Variable names should begin with lower-case letter -->
|
||||
<Rule Id="SA1313" Action="None" /> <!-- Parameter names should begin with lower-case letter -->
|
||||
<Rule Id="SA1314" Action="Warning" /> <!-- Type parameter names should begin with T -->
|
||||
<Rule Id="SX1309" Action="None" /> <!-- Field names should begin with underscore -->
|
||||
<Rule Id="SX1309S" Action="None" /> <!-- Static field names should begin with underscore -->
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.MaintainabilityRules">
|
||||
<Rule Id="SA1119" Action="Warning" /> <!-- Statement should not use unnecessary parenthesis -->
|
||||
<Rule Id="SA1400" Action="Warning" /> <!-- Access modifier should be declared -->
|
||||
<Rule Id="SA1401" Action="Warning" /> <!-- Fields should be private -->
|
||||
<Rule Id="SA1402" Action="None" /> <!-- File may only contain a single type -->
|
||||
<Rule Id="SA1403" Action="Warning" /> <!-- File may only contain a single namespace -->
|
||||
<Rule Id="SA1404" Action="Warning" /> <!-- Code analysis suppression should have justification -->
|
||||
<Rule Id="SA1405" Action="Warning" /> <!-- Debug.Assert should provide message text -->
|
||||
<Rule Id="SA1406" Action="Warning" /> <!-- Debug.Fail should provide message text -->
|
||||
<Rule Id="SA1407" Action="None" /> <!-- Arithmetic expressions should declare precedence -->
|
||||
<Rule Id="SA1408" Action="Warning" /> <!-- Conditional expressions should declare precedence -->
|
||||
<Rule Id="SA1409" Action="None" /> <!-- Remove unnecessary code -->
|
||||
<Rule Id="SA1410" Action="Warning" /> <!-- Remove delegate parenthesis when possible -->
|
||||
<Rule Id="SA1411" Action="Warning" /> <!-- Attribute constructor should not use unnecessary parenthesis -->
|
||||
<Rule Id="SA1412" Action="None" /> <!-- Store files as UTF-8 with byte order mark -->
|
||||
<Rule Id="SA1413" Action="Warning" /> <!-- Use trailing comma in multi-line initializers -->
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.LayoutRules">
|
||||
<Rule Id="SA1500" Action="Warning" /> <!-- Braces for multi-line statements should not share line -->
|
||||
<Rule Id="SA1501" Action="None" /> <!-- Statement should not be on a single line -->
|
||||
<Rule Id="SA1502" Action="Warning" /> <!-- Element should not be on a single line -->
|
||||
<Rule Id="SA1503" Action="None" /> <!-- Braces should not be omitted -->
|
||||
<Rule Id="SA1504" Action="Warning" /> <!-- All accessors should be single-line or multi-line -->
|
||||
<Rule Id="SA1505" Action="Warning" /> <!-- Opening braces should not be followed by blank line -->
|
||||
<Rule Id="SA1506" Action="Warning" /> <!-- Element documentation headers should not be followed by blank line -->
|
||||
<Rule Id="SA1507" Action="Warning" /> <!-- Code should not contain multiple blank lines in a row -->
|
||||
<Rule Id="SA1508" Action="Warning" /> <!-- Closing braces should not be preceded by blank line -->
|
||||
<Rule Id="SA1509" Action="Warning" /> <!-- Opening braces should not be preceded by blank line -->
|
||||
<Rule Id="SA1510" Action="Warning" /> <!-- Chained statement blocks should not be preceded by blank line -->
|
||||
<Rule Id="SA1511" Action="Warning" /> <!-- While-do footer should not be preceded by blank line -->
|
||||
<Rule Id="SA1512" Action="Warning" /> <!-- Single-line comments should not be followed by blank line -->
|
||||
<Rule Id="SA1513" Action="None" /> <!-- Closing brace should be followed by blank line -->
|
||||
<Rule Id="SA1514" Action="Warning" /> <!-- Element documentation header should be preceded by blank line -->
|
||||
<Rule Id="SA1515" Action="None" /> <!-- Single-line comment should be preceded by blank line -->
|
||||
<Rule Id="SA1516" Action="None" /> <!-- Elements should be separated by blank line -->
|
||||
<Rule Id="SA1517" Action="Warning" /> <!-- Code should not contain blank lines at start of file -->
|
||||
<Rule Id="SA1518" Action="Warning" /> <!-- Use line endings correctly at end of file -->
|
||||
<Rule Id="SA1519" Action="Warning" /> <!-- Braces should not be omitted from multi-line child statement -->
|
||||
<Rule Id="SA1520" Action="Warning" /> <!-- Use braces consistently -->
|
||||
</Rules>
|
||||
|
||||
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers.DocumentationRules">
|
||||
<Rule Id="SA1600" Action="None" /> <!-- Elements should be documented -->
|
||||
<Rule Id="SA1601" Action="None" /> <!-- Partial elements should be documented -->
|
||||
<Rule Id="SA1602" Action="None" /> <!-- Enumeration items should be documented -->
|
||||
<Rule Id="SA1603" Action="Warning" /> <!-- Documentation should contain valid XML -->
|
||||
<Rule Id="SA1604" Action="Warning" /> <!-- Element documentation should have summary -->
|
||||
<Rule Id="SA1605" Action="Warning" /> <!-- Partial element documentation should have summary -->
|
||||
<Rule Id="SA1606" Action="Warning" /> <!-- Element documentation should have summary text -->
|
||||
<Rule Id="SA1607" Action="Warning" /> <!-- Partial element documentation should have summary text -->
|
||||
<Rule Id="SA1608" Action="Warning" /> <!-- Element documentation should not have default summary -->
|
||||
<Rule Id="SA1609" Action="None" /> <!-- Property documentation should have value -->
|
||||
<Rule Id="SA1610" Action="Warning" /> <!-- Property documentation should have value text -->
|
||||
<Rule Id="SA1611" Action="Warning" /> <!-- Element parameters should be documented -->
|
||||
<Rule Id="SA1612" Action="Warning" /> <!-- Element parameter documentation should match element parameters -->
|
||||
<Rule Id="SA1613" Action="Warning" /> <!-- Element parameter documentation should declare parameter name -->
|
||||
<Rule Id="SA1614" Action="Warning" /> <!-- Element parameter documentation should have text -->
|
||||
<Rule Id="SA1615" Action="Warning" /> <!-- Element return value should be documented -->
|
||||
<Rule Id="SA1616" Action="Warning" /> <!-- Element return value documentation should have text -->
|
||||
<Rule Id="SA1617" Action="Warning" /> <!-- Void return value should not be documented -->
|
||||
<Rule Id="SA1618" Action="Warning" /> <!-- Generic type parameters should be documented -->
|
||||
<Rule Id="SA1619" Action="Warning" /> <!-- Generic type parameters should be documented partial class -->
|
||||
<Rule Id="SA1620" Action="Warning" /> <!-- Generic type parameter documentation should match type parameters -->
|
||||
<Rule Id="SA1621" Action="Warning" /> <!-- Generic type parameter documentation should declare parameter name -->
|
||||
<Rule Id="SA1622" Action="Warning" /> <!-- Generic type parameter documentation should have text -->
|
||||
<Rule Id="SA1623" Action="Warning" /> <!-- Property summary documentation should match accessors -->
|
||||
<Rule Id="SA1624" Action="Warning" /> <!-- Property summary documentation should omit accessor with restricted access -->
|
||||
<Rule Id="SA1625" Action="Warning" /> <!-- Element documentation should not be copied and pasted -->
|
||||
<Rule Id="SA1626" Action="Warning" /> <!-- Single-line comments should not use documentation style slashes -->
|
||||
<Rule Id="SA1627" Action="Warning" /> <!-- Documentation text should not be empty -->
|
||||
<Rule Id="SA1628" Action="Warning" /> <!-- Documentation text should begin with a capital letter -->
|
||||
<Rule Id="SA1629" Action="Warning" /> <!-- Documentation text should end with a period -->
|
||||
<Rule Id="SA1630" Action="Warning" /> <!-- Documentation text should contain whitespace -->
|
||||
<Rule Id="SA1631" Action="Warning" /> <!-- Documentation should meet character percentage -->
|
||||
<Rule Id="SA1632" Action="Warning" /> <!-- Documentation text should meet minimum character length -->
|
||||
<Rule Id="SA1633" Action="None" /> <!-- File should have header -->
|
||||
<Rule Id="SA1634" Action="Warning" /> <!-- File header should show copyright -->
|
||||
<Rule Id="SA1635" Action="Warning" /> <!-- File header should have copyright text -->
|
||||
<Rule Id="SA1636" Action="Warning" /> <!-- File header copyright text should match -->
|
||||
<Rule Id="SA1637" Action="Warning" /> <!-- File header should contain file name -->
|
||||
<Rule Id="SA1638" Action="Warning" /> <!-- File header file name documentation should match file name -->
|
||||
<Rule Id="SA1639" Action="Warning" /> <!-- File header should have summary -->
|
||||
<Rule Id="SA1640" Action="Warning" /> <!-- File header should have valid company text -->
|
||||
<Rule Id="SA1641" Action="Warning" /> <!-- File header company name text should match -->
|
||||
<Rule Id="SA1642" Action="Warning" /> <!-- Constructor summary documentation should begin with standard text -->
|
||||
<Rule Id="SA1643" Action="Warning" /> <!-- Destructor summary documentation should begin with standard text -->
|
||||
<Rule Id="SA1644" Action="Warning" /> <!-- Documentation headers should not contain blank lines -->
|
||||
<Rule Id="SA1645" Action="Warning" /> <!-- Included documentation file does not exist -->
|
||||
<Rule Id="SA1646" Action="Warning" /> <!-- Included documentation XPath does not exist -->
|
||||
<Rule Id="SA1647" Action="Warning" /> <!-- Include node does not contain valid file and path -->
|
||||
<Rule Id="SA1648" Action="None" /> <!-- Inheritdoc should be used with inheriting class -->
|
||||
<Rule Id="SA1649" Action="None" /> <!-- File name should match first type name -->
|
||||
<Rule Id="SA1650" Action="Warning" /> <!-- Element documentation should be spelled correctly -->
|
||||
<Rule Id="SA1651" Action="Warning" /> <!-- Do not use placeholder elements -->
|
||||
</Rules>
|
||||
</RuleSet>
|
||||
Reference in new issue
Block a user