Compare commits

...
40 Commits
Author SHA1 Message Date
Ang 56c2d9c0df Merge pull request #9 from All-Of-Us-Mods/dev
0.1.3 Changes
2024-08-29 16:51:36 -04:00
ang-xd ab5f997fb6 Bump ver 2024-08-29 16:51:24 -04:00
Ang 869d4970fd Merge pull request #8 from All-Of-Us-Mods/stylecop-and-new-build
Stylecop
2024-08-29 16:47:13 -04:00
ang-xd 2cbbcca161 oops 2024-08-29 16:46:38 -04:00
ang-xd 2f09baa371 Bug fixes and new teleport button 2024-08-29 16:31:07 -04:00
ang-xd acf1d44b52 Changes 2024-08-29 15:47:54 -04:00
ang-xd 03ef684691 New 2024-08-28 20:50:34 -04:00
XtraCube eafc740aca new CI, build, and stylecop skidded from reactor :p 2024-08-28 20:40:56 -04:00
ang-xd 7af85d9fbb Implement custom murders & extension method 2024-08-28 15:18:36 -04:00
Ang bdce8fa37d Update README.md 2024-08-28 15:17:56 -04:00
ang-xd 9fc5044085 Merge branch 'master' into dev 2024-08-28 14:08:12 -04:00
XtraCube 7cb103f318 save config file once when handling netdata 2024-08-28 13:45:32 -04:00
XtraCube dd8d3df2b0 refactor access and management methods for modifiers 2024-08-28 12:39:05 -04:00
XtraCube 19c7e38690 what the freak 2024-08-27 21:56:33 -04:00
XtraCube f1639a784d Merge branch 'refs/heads/dev' 2024-08-27 21:52:41 -04:00
XtraCube 5f1996b457 precautions 2024-08-27 21:49:53 -04:00
XtraCube 33d389e107 fix modifiers logic 2024-08-27 21:46:45 -04:00
XtraCube 176d472cd7 nullable 2024-08-27 21:46:36 -04:00
XtraCube 7a3406e0ae XD 2024-08-27 21:46:26 -04:00
XtraCube 9f92af8958 only show role setting button if there is settings for it 2024-08-27 21:46:21 -04:00
Ang 8d167596d2 Update README.md 2024-08-27 21:29:29 -04:00
XtraCube a98a37bea7 fix modifier manager logic? 2024-08-27 21:14:53 -04:00
ang-xd 4963c80395 Fixes and new stuff 2024-08-27 20:56:53 -04:00
XtraCube 4bef2f838d can use fix 2024-08-27 20:46:15 -04:00
XtraCube 763017b6e2 make GetTarget abstract instead of virtual 2024-08-27 20:44:37 -04:00
XtraCube 16fd8e02aa fix bug with advanced roles settings 2024-08-27 20:44:25 -04:00
XtraCube f187a15f2e more documentaion stuff 2024-08-27 20:23:53 -04:00
XtraCube e211c4c49c rename ResetCooldown to ResetCooldownAndOrEffect, add warning in doc 2024-08-27 20:21:06 -04:00
XtraCube 7f532cd82f Merge remote-tracking branch 'origin/master' 2024-08-27 20:11:43 -04:00
XtraCube 7a0396fe5c Make lobby view pane sync on options change 2024-08-27 20:11:36 -04:00
ang-xd d4955b5183 Merge branch 'master' into dev 2024-08-27 20:10:37 -04:00
ang-xd 0a5c59d9cd somewhat fix notifications on game start 2024-08-27 20:10:01 -04:00
ang-xd 774e1a7a2b Modifiers bug fixed 2024-08-27 19:54:33 -04:00
ang-xd 8e6f75cfbd WIP 2024-08-27 19:43:38 -04:00
XtraCube b36b434a99 oversight in the code 2024-08-27 19:39:36 -04:00
XtraCube 858e518a57 for sake of versioning ig 2024-08-27 19:34:31 -04:00
XtraCube b9734f5a1c forgot to bump version what an idiot 2024-08-27 19:31:42 -04:00
XtraCube 1cb11be9b0 Merge pull request #7 from All-Of-Us-Mods/dev
Button fix, MiraNumberSuffixes, checkmark fix,
2024-08-27 19:24:36 -04:00
XtraCube 6bc6ab7143 fix button issue 2024-08-27 19:22:48 -04:00
ang-xd 05fdf5313d MiraNumberSuffixes, and checkmark fix 2024-08-27 19:04:32 -04:00
52 changed files with 1391 additions and 417 deletions

No files matched your search

+28 -3
View File
@@ -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.repository == 'All-Of-Us-Mods/MiraAPI' && github.ref == 'refs/heads/master' && 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
View File
@@ -4,4 +4,5 @@
obj/
bin/
*.user
*.user
*.lock.json
+14
View File
@@ -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>
+40
View File
@@ -0,0 +1,40 @@
<Project>
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<DebugType>embedded</DebugType>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
<VersionPrefix>0.1.3</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" />
</ItemGroup>
<PropertyGroup>
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)\stylecop.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
</Project>
-29
View File
@@ -1,29 +0,0 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
[RegisterButton]
public class ExampleButton : CustomActionButton
{
public override string Name => "Example Button";
public override float Cooldown => 5f;
public override float EffectDuration => 0f;
public override int MaxUses => 5;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override ButtonLocation Location => ButtonLocation.BottomRight;
protected override void OnClick()
{
Logger<ExamplePlugin>.Info("Example button clicked!");
}
public override bool Enabled(RoleBehaviour role)
{
return PlayerControl.LocalPlayer.HasModifier<GameModifierExample>();
}
}
@@ -1,20 +1,24 @@
using MiraAPI.Hud;
using MiraAPI.Example.Modifiers.Freezer;
using MiraAPI.Example.Roles;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
namespace MiraAPI.Example.Buttons.Freezer;
public class ExampleTargetButton : CustomActionButton<PlayerControl>
[RegisterButton]
public class FreezeButton : CustomActionButton<PlayerControl>
{
public override string Name => "Shoot";
public override float Cooldown => 0f;
public override string Name => "Freeze";
public override float Cooldown => 5f;
public override float EffectDuration => 0f;
public override int MaxUses => 0;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
protected override void OnClick()
{
PlayerControl.LocalPlayer.CmdCheckMurder(Target);
Target?.RpcAddModifier<FreezeModifier>();
}
public override PlayerControl GetTarget()
@@ -24,17 +28,17 @@ public class ExampleTargetButton : CustomActionButton<PlayerControl>
public override void SetOutline(bool active)
{
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Color.red));
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue));
}
public override bool IsTargetValid(PlayerControl target)
{
return PlayerControl.LocalPlayer.Data.Role.IsValidTarget(target.Data);
return true;
}
public override bool Enabled(RoleBehaviour role)
{
return role.IsImpostor;
return role is FreezerRole;
}
}
+47
View File
@@ -0,0 +1,47 @@
using MiraAPI.Example;
using MiraAPI.Example.Modifiers;
using MiraAPI.Hud;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
[RegisterButton]
public class MeetingButton : CustomActionButton
{
public override string Name => "Call Meeting";
public override float Cooldown => 15;
public override float EffectDuration => 0;
public override int MaxUses => 3;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override bool Enabled(RoleBehaviour role)
{
return PlayerControl.LocalPlayer.HasModifier<CaptainModifier>();
}
protected override void OnClick()
{
var bt = ShipStatus.Instance.EmergencyButton;
PlayerControl.LocalPlayer.NetTransform.Halt();
var minigame = Object.Instantiate(bt.MinigamePrefab, Camera.main!.transform, false);
var taskAdderGame = minigame as TaskAdderGame;
if (taskAdderGame != null)
{
taskAdderGame.SafePositionWorld = bt.SafePositionLocal + (Vector2)bt.transform.position;
}
minigame.transform.localPosition = new Vector3(0f, 0f, -50f);
minigame.Begin(null);
if (UsesLeft == 0 && PlayerControl.LocalPlayer.HasModifier<CaptainModifier>())
{
PlayerControl.LocalPlayer.RpcRemoveModifier<CaptainModifier>();
}
}
}
@@ -0,0 +1,86 @@
using MiraAPI.Example.Options.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using System.Collections;
using UnityEngine;
namespace MiraAPI.Example.Buttons.Teleporter;
[RegisterButton]
public class TeleportButton : CustomActionButton
{
public override string Name => "Teleport";
public override float Cooldown => OptionGroupSingleton<TeleporterOptions>.Instance.TeleportCooldown;
public override float EffectDuration => OptionGroupSingleton<TeleporterOptions>.Instance.TeleportDuration;
public override int MaxUses => 0;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
public static bool IsZoom { get; private set; }
public override bool Enabled(RoleBehaviour role)
{
return role is TeleporterRole;
}
protected override void OnClick()
{
Coroutines.Start(ZoomOutCoroutine());
}
public override void OnEffectEnd()
{
Coroutines.Start(ZoomInCoroutine());
}
protected override void FixedUpdate(PlayerControl playerControl)
{
base.FixedUpdate(playerControl);
if (!EffectActive) return;
if (Input.GetKey(KeyCode.Mouse0))
{
playerControl.NetTransform.RpcSnapTo(Camera.main.ScreenToWorldPoint(Input.mousePosition));
ResetCooldownAndOrEffect();
}
}
private static IEnumerator ZoomOutCoroutine()
{
HudManager.Instance.ShadowQuad.gameObject.SetActive(false);
IsZoom = true;
var zoomDistance = OptionGroupSingleton<TeleporterOptions>.Instance.ZoomDistance;
for (var ft = Camera.main!.orthographicSize; ft < zoomDistance; ft += 0.3f)
{
Camera.main.orthographicSize = MeetingHud.Instance ? 3f : ft;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
foreach (var cam in Camera.allCameras) cam.orthographicSize = Camera.main.orthographicSize;
yield return null;
}
foreach (var cam in Camera.allCameras) cam.orthographicSize = zoomDistance;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
}
private static IEnumerator ZoomInCoroutine()
{
for (var ft = Camera.main!.orthographicSize; ft > 3f; ft -= 0.3f)
{
Camera.main.orthographicSize = MeetingHud.Instance ? 3f : ft;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
foreach (var cam in Camera.allCameras) cam.orthographicSize = Camera.main.orthographicSize;
yield return null;
}
foreach (var cam in Camera.allCameras) cam.orthographicSize = 3f;
HudManager.Instance.ShadowQuad.gameObject.SetActive(true);
IsZoom = false;
ResolutionManager.ResolutionChanged.Invoke((float)Screen.width / Screen.height, Screen.width, Screen.height, Screen.fullScreen);
}
}
+3
View File
@@ -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");
}
+4 -21
View File
@@ -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>
+4 -4
View File
@@ -1,8 +1,8 @@
using MiraAPI.Utilities;
using MiraAPI.Example.Modifiers;
using MiraAPI.Utilities;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using System;
using MiraAPI.Example.Modifiers;
using UnityEngine;
namespace MiraAPI.Example;
@@ -13,11 +13,11 @@ public class MiraDebugWindow(IntPtr ptr) : MonoBehaviour(ptr)
{
if (GUILayout.Button("Test modifier"))
{
PlayerControl.LocalPlayer.AddModifier<ModifierTimerExample>();
//PlayerControl.LocalPlayer.AddModifier<ModifierTimerExample>();
}
if (GUILayout.Button("Remove modifier"))
{
PlayerControl.LocalPlayer.RemoveModifier<ModifierTimerExample>();
//PlayerControl.LocalPlayer.RemoveModifier<ModifierTimerExample>();
}
})
{
@@ -4,15 +4,9 @@ using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
[RegisterModifier]
public class GameModifierExample : GameModifier
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Example Game Modifier";
public override bool CanVent()
{
return true;
}
public override string ModifierName => "Captain";
public override int GetAmountPerGame()
{
return 1;
@@ -0,0 +1,30 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers.Freezer;
[RegisterModifier]
public class FreezeModifier : TimedModifier
{
public override string ModifierName => "Freezed";
public override bool HideOnUi => false;
public override float Duration => 15f;
public override bool AutoStart => true;
public override bool RemoveOnComplete => true;
public override void OnActivate()
{
if (Player.AmOwner)
{
Player.moveable = false;
}
}
public override void OnTimerComplete()
{
if (Player.AmOwner)
{
Player.moveable = true;
}
}
}
@@ -1,21 +0,0 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
[RegisterModifier]
public class ModifierTimerExample : TimedModifier
{
public override string ModifierName => "Color Changer";
public override bool HideOnUi => true;
public override float Duration => 10f;
public override bool AutoStart => true;
private readonly System.Random _rand = new();
public override void OnTimerComplete()
{
Player.SetColor((byte)_rand.Next(0, Palette.PlayerColors.Count));
StartTimer();
}
}
+8 -7
View File
@@ -1,5 +1,6 @@
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Options;
@@ -15,19 +16,19 @@ public class ExampleOptions : AbstractOptionGroup
[ModdedToggleOption("Toggle Opt 2")]
public bool ToggleOpt2 { get; set; } = true;
[ModdedNumberOption("Number Opt", min: 0, max: 10)]
[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(BestApiEnum), ["Mira API", "Mitochondria", "Reactor"])]
public BestApiEnum EnumOpt { get; set; } = BestApiEnum.MiraAPI;
}
public enum TestEnum
public enum BestApiEnum
{
Welcome,
To,
Mira
MiraAPI,
Mitochondria,
Reactor
}
@@ -9,7 +9,7 @@ public class CustomRole2Settings : AbstractOptionGroup
{
public override string GroupName => "Custom Role";
public override Type AdvancedRole => typeof(CustomRole2);
public override Type AdvancedRole => typeof(FreezerRole);
[ModdedToggleOption("Teleportation ability")] public bool Teleport { get; set; } = true;
@@ -1,16 +0,0 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using System;
namespace MiraAPI.Example.Options.Roles;
public class CustomRoleSettings : AbstractOptionGroup
{
public override string GroupName => "Custom Role";
public override Type AdvancedRole => typeof(CustomRole);
[ModdedNumberOption("Number Opt", min: 3, max: 9)] public float NumberOpt { get; set; } = 4;
}
@@ -0,0 +1,23 @@
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
using System;
namespace MiraAPI.Example.Options.Roles;
public class TeleporterOptions : AbstractOptionGroup
{
public override string GroupName => "Teleporter";
public override Type AdvancedRole => typeof(TeleporterRole);
[ModdedNumberOption("Teleport Cooldown", 5, 60, 2.5f, MiraNumberSuffixes.Seconds)]
public float TeleportCooldown { get; set; } = 5;
[ModdedNumberOption("Teleport Duration", 5, 25, 1, MiraNumberSuffixes.Seconds)]
public float TeleportDuration { get; set; } = 10;
[ModdedNumberOption("Zoom Distance", 4, 15)]
public float ZoomDistance { get; set; } = 6;
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

@@ -5,13 +5,13 @@ using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole : ImpostorRole, ICustomRole
public class FreezerRole : ImpostorRole, ICustomRole
{
public string RoleName => "Custom Role";
public string RoleLongDescription => "Eliminate everyone with your special ability!";
public string RoleName => "Freezer";
public string RoleLongDescription => "Freeze another player for a duration of time.";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.Black;
public Color RoleColor => Palette.Blue;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
public int MaxPlayers => 2;
}
@@ -5,12 +5,12 @@ using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole2 : CrewmateRole, ICustomRole
public class TeleporterRole : CrewmateRole, ICustomRole
{
public string RoleName => "Custom Role 2";
public string RoleLongDescription => "Save everyone from dying!";
public string RoleName => "Teleporter";
public string RoleLongDescription => "Zoom out and teleport across the map!";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.AcceptedGreen;
public Color RoleColor => new Color32(221, 176, 152, 255);
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
@@ -4,12 +4,12 @@ using System.Reflection;
namespace MiraAPI.GameOptions.Attributes;
public class ModdedEnumOptionAttribute(string title, Type enumType, Type roleType = null)
public class ModdedEnumOptionAttribute(string title, Type enumType, string[] values = null, Type roleType = null)
: ModdedOptionAttribute(title, roleType)
{
internal override IModdedOption CreateOption(object value, PropertyInfo property)
{
var opt = new ModdedEnumOption(Title, (int)value, enumType, RoleType);
var opt = new ModdedEnumOption(Title, (int)value, enumType, values, RoleType);
return opt;
}
@@ -1,4 +1,5 @@
using MiraAPI.GameOptions.OptionTypes;
using MiraAPI.Utilities;
using System;
using System.Reflection;
@@ -9,7 +10,7 @@ public class ModdedNumberOptionAttribute(
float min,
float max,
float increment = 1,
NumberSuffixes suffixType = NumberSuffixes.None,
MiraNumberSuffixes suffixType = MiraNumberSuffixes.None,
bool zeroInfinity = false,
Type roleType = null)
: ModdedOptionAttribute(title, roleType)
@@ -120,6 +120,14 @@ 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
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values)
{
plugin.PluginConfig.SaveOnConfigSet = false;
}
foreach (var netData in data)
{
if (!ModdedOptions.TryGetValue(netData.Id, out var option))
@@ -129,6 +137,17 @@ public static class ModdedOptionsManager
option.HandleNetData(netData.Data);
}
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values)
{
plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = true;
}
if (LobbyInfoPane.Instance)
{
LobbyInfoPane.Instance.RefreshPane();
}
}
@@ -1,7 +1,7 @@
using Reactor.Localization.Utilities;
using MiraAPI.Networking;
using Reactor.Localization.Utilities;
using System;
using System.Linq;
using MiraAPI.Networking;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -11,15 +11,18 @@ public class ModdedEnumOption : ModdedOption<int>
{
public string[] Values { get; }
public ModdedEnumOption(string title, int defaultValue, Type enumType, Type roleType=null) : base(title, defaultValue, roleType)
public ModdedEnumOption(string title, int defaultValue, Type enumType, string[] values = null, Type roleType = null) : base(title, defaultValue, roleType)
{
Values = Enum.GetNames(enumType);
Values = values is null ? Enum.GetNames(enumType) : 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.Values = values is null ?
Enum.GetNames(enumType).Select(CustomStringName.CreateAndRegister).ToArray()
: values.Select(CustomStringName.CreateAndRegister).ToArray();
data.Index = Value;
}
@@ -29,7 +32,7 @@ public class ModdedEnumOption : ModdedOption<int>
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;
@@ -1,7 +1,7 @@
using System;
using AmongUs.GameOptions;
using AmongUs.GameOptions;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using System;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -9,17 +9,17 @@ namespace MiraAPI.GameOptions.OptionTypes;
public class ModdedNumberOption : ModdedOption<float>
{
public float Min { get;}
public float Min { get; }
public float Max { get; }
public float Increment { get; }
public NumberSuffixes SuffixType { get; }
public MiraNumberSuffixes SuffixType { get; }
public bool ZeroInfinity { get; }
public ModdedNumberOption(string title, float defaultValue, float min, float max, float increment, NumberSuffixes suffixType, bool zeroInfinity=false, Type roleType=null) : base(title, defaultValue, roleType)
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;
@@ -28,18 +28,18 @@ public class ModdedNumberOption : ModdedOption<float>
ZeroInfinity = zeroInfinity;
Value = Mathf.Clamp(defaultValue, min, max);
Data = ScriptableObject.CreateInstance<FloatGameSetting>();
var data = (FloatGameSetting)Data;
data.Type = global::OptionTypes.Float;
data.Title = StringName;
data.Value = Value;
data.Increment = Increment;
data.ValidRange = new FloatRange(Min, Max);
data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0";
data.FormatString = Increment % 1 == 0 && Value % 1 == 0 && Min % 1 == 0 && Max % 1 == 0 ? "0" : "0.0";
data.ZeroIsInfinity = ZeroInfinity;
data.SuffixType = SuffixType;
data.SuffixType = (NumberSuffixes)SuffixType;
data.OptionName = FloatOptionNames.Invalid;
}
@@ -49,17 +49,17 @@ public class ModdedNumberOption : ModdedOption<float>
numberOption.SetUpFromData(Data, 20);
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
numberOption.Title = StringName;
numberOption.Value = Value;
numberOption.Increment = Increment;
numberOption.ValidRange = new FloatRange(Min, Max);
numberOption.FormatString = ((FloatGameSetting)Data).FormatString;
numberOption.ZeroIsInfinity = ZeroInfinity;
numberOption.SuffixType = SuffixType;
numberOption.SuffixType = (NumberSuffixes)SuffixType;
numberOption.floatOptionName = FloatOptionNames.Invalid;
OptionBehaviour = numberOption;
return numberOption;
@@ -66,14 +66,14 @@ public abstract class ModdedOption<T> : IModdedOption
if (AmongUsClient.Instance.AmHost)
{
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
{
entry.Value = Value;
}
Rpc<SyncOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [GetNetData()], true);
}
if (ParentMod?.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry) == true)
{
entry.Value = Value;
}
OnValueChanged(newValue);
}
+83 -30
View File
@@ -1,8 +1,8 @@
#nullable enable
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace MiraAPI.Hud;
@@ -54,23 +54,23 @@ public abstract class CustomActionButton
/// <summary>
/// Returns true if the effect is currently active.
/// </summary>
protected bool EffectActive;
/// <summary>
/// A timer variable to measure cooldowns and effects.
/// </summary>
protected float Timer;
public bool EffectActive { get; protected set; }
/// <summary>
/// Returns the amount of uses left.
/// </summary>
protected int UsesLeft;
public int UsesLeft { get; protected set; }
/// <summary>
/// A timer variable to measure cooldowns and effects.
/// </summary>
public float Timer { get; protected set; }
/// <summary>
/// The button object in game. This is created by Mira API automatically.
/// </summary>
protected ActionButton? Button { get; private set; }
internal void CreateButton(Transform parent)
{
if (Button)
@@ -95,24 +95,24 @@ public abstract class CustomActionButton
}
var pb = Button.GetComponent<PassiveButton>();
pb.OnClick.RemoveAllListeners();
pb.OnClick = new Button.ButtonClickedEvent();
pb.OnClick.AddListener((UnityAction)ClickHandler);
}
/// <summary>
/// A utility function to reset the cooldown of the button.
/// A utility function to reset the cooldown and/or effect of the button.
/// </summary>
public void ResetCooldown()
public void ResetCooldownAndOrEffect()
{
Timer = Cooldown;
if (EffectActive)
{
OnEffectEnd();
}
EffectActive = false;
}
/// <summary>
/// A utility function to override the sprite of the button.
/// </summary>
@@ -135,13 +135,68 @@ public abstract class CustomActionButton
}
/// <summary>
/// A utility function that runs with the local PlayerControl's FixedUpdate.
/// Set the button's timer.
/// </summary>
/// <param name="amount">The amount 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) { }
/// <summary>
/// Runs when the button is clicked.
/// Callback method for the button click event.
/// </summary>
protected abstract void OnClick();
@@ -153,7 +208,8 @@ public abstract class CustomActionButton
public abstract bool Enabled(RoleBehaviour? role);
/// <summary>
/// Given that there is an effect, this method that runs when the effect ends.
/// 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() { }
@@ -238,7 +294,7 @@ public abstract class CustomActionButton
{
Button?.SetDisabled();
}
Button?.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown);
FixedUpdate(playerControl);
@@ -255,12 +311,12 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
/// The target object of the button.
/// </summary>
public T? Target { get; private set; }
/// <summary>
/// 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>
@@ -269,26 +325,23 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
/// <summary>
/// Determines if the target object is valid.
/// </summary>
public virtual bool IsTargetValid(T target)
public virtual bool IsTargetValid(T? target)
{
return target;
}
/// <summary>
/// The method used to get the target object.
/// </summary>
public virtual T? GetTarget()
{
return PlayerControl.LocalPlayer.GetNearestObjectOfType<T>(Distance, ColliderTag, IsTargetValid);
}
public abstract T? GetTarget();
/// <summary>
/// Sets the outline of the target object.
/// </summary>
/// <param name="active">Should the outline be active</param>
public abstract void SetOutline(bool active);
public override bool CanUse()
{
var newTarget = GetTarget();
@@ -297,9 +350,9 @@ public abstract class CustomActionButton<T> : CustomActionButton where T : MonoB
SetOutline(false);
}
Target = newTarget;
Target = IsTargetValid(newTarget) ? newTarget : null;
SetOutline(true);
return base.CanUse() && Target;
}
}
+6 -29
View File
@@ -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.0</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>
+1 -1
View File
@@ -26,7 +26,7 @@ public partial class MiraApiPlugin : BasePlugin
{
Harmony.PatchAll();
ReactorCredits.Register("Mira API", Version.Truncate(11, "") ?? Version, true, ReactorCredits.AlwaysShow);
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
PluginManager = new MiraPluginManager();
PluginManager.Initialize();
+84 -43
View File
@@ -1,7 +1,5 @@
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
using MiraAPI.Utilities;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using System;
@@ -38,23 +36,31 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
public void FixedUpdate()
{
if (ActiveModifiers is null) return;
foreach (var modifier in ActiveModifiers)
{
if (modifier is null) continue;
modifier.FixedUpdate();
}
}
public void Update()
{
if (ActiveModifiers is null) return;
foreach (var modifier in ActiveModifiers)
{
if (modifier is null) continue;
modifier.Update();
}
var filteredModifiers = ActiveModifiers.Where(mod => !mod.HideOnUi);
var baseModifiers = filteredModifiers as BaseModifier[] ?? filteredModifiers.ToArray();
if (player.AmOwner && baseModifiers.Any())
{
var stringBuild = new StringBuilder();
@@ -74,17 +80,9 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
}
}
public static void RemoveModifier(PlayerControl target, uint modifierId)
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 = ActiveModifiers.Find(x => x.GetType() == type);
if (modifier is null)
{
@@ -92,58 +90,101 @@ public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
return;
}
modifier.OnDeactivate();
modifierComponent.ActiveModifiers.Remove(modifier);
RemoveModifier(modifier);
}
if (target.AmOwner)
public void RemoveModifier<T>() where T : BaseModifier
{
RemoveModifier(typeof(T));
}
public void RemoveModifier(uint modifierId)
{
var modifier = ActiveModifiers.Find(x => x.ModifierId == modifierId);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with id {modifierId} because it is not active.");
return;
}
RemoveModifier(modifier);
}
public void RemoveModifier(BaseModifier modifier)
{
if (!ActiveModifiers.Contains(modifier))
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
return;
}
modifier.OnDeactivate();
ActiveModifiers.Remove(modifier);
if (player.AmOwner)
{
HudManager.Instance.SetHudActive(true);
}
}
public static void AddModifier(PlayerControl target, uint modifierId)
public BaseModifier AddModifier(BaseModifier modifier)
{
if (!ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
if (ActiveModifiers.Contains(modifier))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return;
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
return null;
}
if (target.HasModifier(modifierId))
ActiveModifiers.Add(modifier);
modifier.Player = player;
modifier.ModifierId = ModifierManager.TypeToIdModifiers[modifier.GetType()];
modifier.OnActivate();
if (!player.AmOwner)
{
return modifier;
}
if (modifier is TimedModifier { AutoStart: true } timer)
{
timer.StartTimer();
}
HudManager.Instance.SetHudActive(true);
return modifier;
}
public BaseModifier AddModifier(Type type)
{
if (!ModifierManager.TypeToIdModifiers.TryGetValue(type, out var modifierId))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is not registered.");
return null;
}
if (ActiveModifiers.Find(x => x.ModifierId == modifierId) != null)
{
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifierId}!");
return;
return null;
}
var modifier = (BaseModifier)Activator.CreateInstance(type);
var modifierComponent = target.GetModifierComponent();
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return;
return null;
}
modifierComponent.ActiveModifiers.Add(modifier);
modifier.Player = modifierComponent.player;
modifier.ModifierId = modifierId;
modifier.OnActivate();
AddModifier(modifier);
if (target.AmOwner)
{
if (modifier is TimedModifier { AutoStart: true } timer)
{
timer.StartTimer();
}
HudManager.Instance.SetHudActive(true);
}
return modifier;
}
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(PlayerControl target, uint modifierId) => RemoveModifier(target, modifierId);
[MethodRpc((uint)MiraRpc.AddModifier)]
public static void RpcAddModifier(PlayerControl target, uint modifierId) => AddModifier(target, modifierId);
public T AddModifier<T>() where T : BaseModifier
{
return AddModifier(typeof(T)) as T;
}
}
+53 -28
View File
@@ -6,6 +6,8 @@ using Reactor.Networking.Rpc;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using Reactor.Utilities;
using Random = System.Random;
namespace MiraAPI.Modifiers;
@@ -40,12 +42,27 @@ public static class ModifierManager
List<uint> filteredModifiers = [];
foreach (var modifier in IdToTypeModifiers)
foreach (var modifier in IdToTypeModifiers.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier))))
{
var mod = (GameModifier)Activator.CreateInstance(modifier.Value);
var num = mod.GetAmountPerGame();
var chance = mod.GetAssignmentChance();
if (mod is null)
{
Logger<MiraApiPlugin>.Error($"Failed to create instance of {modifier.Value.Name}");
continue;
}
if (!plrs.Any(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 num = Math.Clamp(mod.GetAmountPerGame(), 0, maxCount);
var chance = mod.GetAssignmentChance();
for (var i = 0; i < num; i++)
{
var randomNum = rand.Next(100);
@@ -62,52 +79,54 @@ 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)
{
Logger<MiraApiPlugin>.Error($"Failed to create instance of {IdToTypeModifiers[id].Name}");
continue;
}
if (!plrs.Any(x=>IsGameModifierValid(x, mod, id)))
{
shuffledModifiers.RemoveAt(0);
continue;
}
var plr = plrs.Random();
if (plr.Data.Role is ICustomRole modRole)
{
if (!modRole.IsModifierApplicable(mod))
{
continue;
}
}
if (plr.HasModifier(id) || mod.IsModifierValidOn(plr.Data.Role))
if (!IsGameModifierValid(plr, mod, id))
{
continue;
}
shuffledModifiers.RemoveAt(0);
ModifierComponent.RpcAddModifier(plr, id);
plr.RpcAddModifier(id);
}
}
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) &&
!player.HasModifier(modifierId);
}
internal static void SyncAllModifiers(int targetId = -1)
{
var data = new List<NetData>();
if (targetId == -1)
foreach (var player in GameData.Instance.AllPlayers)
{
foreach (var player in GameData.Instance.AllPlayers)
{
data.Add(GetPlayerModifiers(player.Object));
}
}
else
{
var player = GameData.Instance.GetPlayerById((byte)targetId).Object;
data.Add(GetPlayerModifiers(player));
data.Add(GetPlayerModifiers(player.Object));
}
Rpc<SyncModifiersRpc>.Instance.Send(PlayerControl.LocalPlayer, data.ToArray(), true);
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
}
internal static void HandleSyncModifiers(NetData[] data)
@@ -133,7 +152,13 @@ public static class ModifierManager
foreach (var id in ids)
{
ModifierComponent.AddModifier(plr, id);
if (!IdToTypeModifiers.TryGetValue(id, out var type))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {id} because it is not registered.");
continue;
}
modifierComponent.AddModifier(type);
}
}
}
+8 -1
View File
@@ -1,4 +1,5 @@
using Reactor.Utilities;
using MiraAPI.Utilities;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Modifiers.Types;
@@ -6,6 +7,7 @@ public abstract class TimedModifier : BaseModifier
{
public abstract float Duration { get; }
public virtual bool AutoStart => false;
public virtual bool RemoveOnComplete => true;
public abstract void OnTimerComplete();
public bool TimerActive { get; protected set; }
@@ -28,6 +30,11 @@ public abstract class TimedModifier : BaseModifier
TimerActive = false;
TimeRemaining = Duration;
OnTimerComplete();
if (RemoveOnComplete)
{
Player.RpcRemoveModifier(ModifierId);
}
}
}
+186
View File
@@ -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)
{
MurderResultFlags murderResultFlags = didSucceed ? MurderResultFlags.Succeeded : MurderResultFlags.FailedError;
MurderResultFlags 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);
NetworkedPlayerInfo data = target.Data;
if (resultFlags.HasFlag(MurderResultFlags.FailedError))
{
return;
}
if (resultFlags.HasFlag(MurderResultFlags.FailedProtected) || (resultFlags.HasFlag(MurderResultFlags.DecisionByHost) && target.protectedByGuardianId > -1))
{
target.protectedByGuardianThisRound = true;
bool 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)
{
FollowerCamera cam = Camera.main.GetComponent<FollowerCamera>();
bool isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
PlayerPhysics 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);
Vector3 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;
}
}
+2 -1
View File
@@ -6,5 +6,6 @@ public enum MiraRpc : uint
SyncRoleOptions,
AddModifier,
RemoveModifier,
SyncModifiers
SyncModifiers,
CustomMurder,
}
+1 -1
View File
@@ -12,7 +12,7 @@ public static class MeetingHudPatches
{
foreach (var customActionButton in CustomButtonManager.CustomButtons)
{
customActionButton.ResetCooldown();
customActionButton.ResetCooldownAndOrEffect();
}
}
}
@@ -3,7 +3,6 @@ using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Localization.Utilities;
using Reactor.Utilities.Extensions;
using System.Collections.Generic;
using System.Linq;
using TMPro;
using UnityEngine;
@@ -102,7 +101,7 @@ public static class GameOptionsMenuPatch
var options = group.Options.Select(opt => opt.CreateOption(__instance.checkboxOrigin,
__instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.settingsContainer));
foreach (var newOpt in options)
{
newOpt.SetClickMask(__instance.ButtonClickMask);
@@ -137,10 +136,10 @@ public static class GameOptionsMenuPatch
if (newOpt is ToggleOption toggle)
{
toggle.CheckMark.sprite = MiraAssets.Checkmark.LoadAsset();
toggle.CheckMark.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
var rend = toggle.CheckMark.transform.parent.FindChild("ActiveSprite").GetComponent<SpriteRenderer>();
rend.sprite = MiraAssets.CheckmarkBox.LoadAsset();
rend.color = group.GroupColor;
rend.color = group.GroupColor != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
}
__instance.Children.Add(newOpt);
@@ -1,4 +1,5 @@
using HarmonyLib;
#nullable enable
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
@@ -17,9 +18,9 @@ public static class GameSettingMenuPatches
{
public static int CurrentSelectedMod { get; private set; }
public static MiraPluginInfo SelectedMod { get; private set; }
public static MiraPluginInfo? SelectedMod { get; private set; }
private static TextMeshPro _text;
private static TextMeshPro? _text;
private static Vector3 _roleBtnOgPos;
@@ -80,12 +81,12 @@ public static class GameSettingMenuPatches
private static void UpdateText(GameSettingMenu menu, GameOptionsMenu settings, RolesSettingsMenu roles)
{
if (CurrentSelectedMod == 0)
if (_text is not null && CurrentSelectedMod == 0)
{
_text.text = "Default";
_text.fontSizeMax = 3.2f;
}
else
else if (_text is not null)
{
_text.fontSizeMax = 2.3f;
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod - 1).Value;
@@ -153,7 +154,7 @@ public static class GameSettingMenuPatches
roles.scrollBar.ScrollToTop();
}
if (settings?.Children != null && SelectedMod?.OptionGroups.Count != 0)
if (settings.Children != null && SelectedMod?.OptionGroups.Count != 0)
{
foreach (var child in settings.Children)
{
@@ -0,0 +1,39 @@
using HarmonyLib;
using Il2CppSystem;
using MiraAPI.Roles;
using System.Linq;
namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(NotificationPopper))]
public class NotificationPopperPatch
{
[HarmonyPrefix]
[HarmonyPatch(nameof(NotificationPopper.AddRoleSettingsChangeMessage))]
public static bool RoleChangeMsgPatch(NotificationPopper __instance,
[HarmonyArgument(0)] StringNames key, [HarmonyArgument(1)] int roleCount, [HarmonyArgument(2)] int roleChance, [HarmonyArgument(3)] RoleTeamTypes teamType,
[HarmonyArgument(4)] bool playSound)
{
if (CustomRoleManager.CustomRoles.Values.Any(role => role.StringName == key))
{
string item = string.Empty;
string text = (teamType == RoleTeamTypes.Crewmate) ? Palette.CrewmateSettingChangeText.ToTextColor() : Palette.ImpostorRed.ToTextColor();
item = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.LobbyChangeSettingNotificationRole, new Object[]
{
string.Concat(
[
"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">",
text,
DestroyableSingleton<TranslationController>.Instance.GetString(key, Array.Empty<Object>()),
"</color></font>"
]),
"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">" + roleCount.ToString() + "</font>",
"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">" + roleChance.ToString() + "%"
});
__instance.SettingsChangeMessageLogic(key, item, playSound);
return false;
}
return true;
}
}
@@ -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;
@@ -225,10 +225,19 @@ public static class RoleSettingMenuPatches
__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);
CreateAdvancedSettings(__instance, role);
foreach (var optionBehaviour in __instance.advancedSettingChildren)
{
if (optionBehaviour.IsCustom())
{
continue;
}
optionBehaviour.OnValueChanged = new Action<OptionBehaviour>(__instance.ValueChanged);
if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost)
{
@@ -255,26 +264,27 @@ public static class RoleSettingMenuPatches
roleOptionSetting.titleText.color = customRole.RoleColor.GetAlternateColor();
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
newButton.name = "ConfigButton";
newButton.transform.localPosition = new Vector3(0.4473f, -0.3f, -2f);
newButton.transform.FindChild("Text_TMP").gameObject.DestroyImmediate();
newButton.activeSprites.Destroy();
var btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent<SpriteRenderer>();
btnRend.sprite = MiraAssets.Cog.LoadAsset();
var passiveButton = newButton.GetComponent<GameOptionButton>();
passiveButton.OnClick = new ButtonClickedEvent();
passiveButton.interactableColor = btnRend.color = customRole.RoleColor.GetAlternateColor();
passiveButton.interactableHoveredColor = Color.white;
passiveButton.OnClick.AddListener((UnityAction)(() =>
if (GameSettingMenuPatches.SelectedMod is null ||
GameSettingMenuPatches.SelectedMod.Options.Any(x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role)))
{
ChangeTab(role, __instance);
}));
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
newButton.name = "ConfigButton";
newButton.transform.localPosition = new Vector3(0.4473f, -0.3f, -2f);
newButton.transform.FindChild("Text_TMP").gameObject.DestroyImmediate();
newButton.activeSprites.Destroy();
if (index < GameSettingMenuPatches.SelectedMod.CustomRoles.Count - 1)
var btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent<SpriteRenderer>();
btnRend.sprite = MiraAssets.Cog.LoadAsset();
var passiveButton = newButton.GetComponent<GameOptionButton>();
passiveButton.OnClick = new ButtonClickedEvent();
passiveButton.interactableColor = btnRend.color = customRole.RoleColor.GetAlternateColor();
passiveButton.interactableHoveredColor = Color.white;
passiveButton.OnClick.AddListener((UnityAction)(() => { ChangeTab(role, __instance); }));
}
if (index < GameSettingMenuPatches.SelectedMod?.CustomRoles.Count - 1)
{
yPos += -0.43f;
}
+42
View File
@@ -0,0 +1,42 @@
using HarmonyLib;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Patches.Options;
[HarmonyPatch]
public class SettingPatches
{
[HarmonyPrefix]
[HarmonyPatch(typeof(FloatGameSetting), nameof(FloatGameSetting.GetValueString))]
public static bool ValueStringPatch(FloatGameSetting __instance, ref string __result, [HarmonyArgument(0)] float value)
{
string result = string.Empty;
MiraNumberSuffixes suffix = (MiraNumberSuffixes)__instance.SuffixType;
if (__instance.ZeroIsInfinity && Mathf.Abs(value) < 0.0001f)
{
result = "<b>∞</b>";
}
else if (suffix == MiraNumberSuffixes.None)
{
result = value.ToString(__instance.FormatString);
}
else if (suffix == MiraNumberSuffixes.Multiplier)
{
result = value.ToString(__instance.FormatString) + "x";
}
else if (suffix == MiraNumberSuffixes.Percent)
{
result = value.ToString(__instance.FormatString) + "%";
}
else
{
result = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.GameSecondsAbbrev, (Il2CppSystem.Object[])(
[
value.ToString(__instance.FormatString)
]));
}
__result = result;
return false;
}
}
+2 -2
View File
@@ -37,9 +37,9 @@ public static class PlayerControlPatches
foreach (var button in CustomButtonManager.CustomButtons)
{
if (__instance.Data?.Role != null)
if (__instance.Data?.Role == null)
{
return;
continue;
}
if (!button.Enabled(__instance.Data?.Role))
Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 B

After

Width:  |  Height:  |  Size: 1.0 KiB

+34 -13
View File
@@ -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,8 +11,6 @@ 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;
@@ -20,11 +20,11 @@ namespace MiraAPI.Roles;
public static class CustomRoleManager
{
public static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = new();
public static readonly Dictionary<Type, ushort> RoleIds = new();
private static ushort _roleId = 100;
private static ushort GetNextRoleId()
{
return _roleId++;
@@ -37,8 +37,8 @@ public static class CustomRoleManager
public 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);
@@ -68,7 +68,7 @@ public static class CustomRoleManager
roleBehaviour.gameObject.Destroy();
return null;
}
var roleId = RoleIds[roleType];
roleBehaviour.Role = (RoleTypes)roleId;
@@ -83,7 +83,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)
@@ -101,7 +101,7 @@ public static class CustomRoleManager
var config = parentMod.PluginConfig;
config.Bind(customRole.NumConfigDefinition, 1);
config.Bind(customRole.ChanceConfigDefinition, 100);
return roleBehaviour;
}
@@ -156,8 +156,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 +172,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.Values)
{
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,7 +197,7 @@ 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
@@ -205,5 +215,16 @@ public static class CustomRoleManager
Logger<MiraApiPlugin>.Error(e);
}
}
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values)
{
plugin.PluginConfig.Save();
plugin.PluginConfig.SaveOnConfigSet = oldConfigSetting[plugin];
}
if (LobbyInfoPane.Instance)
{
LobbyInfoPane.Instance.RefreshPane();
}
}
}
+6 -4
View File
@@ -3,11 +3,11 @@ using BepInEx.Configuration;
using HarmonyLib;
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;
@@ -24,6 +24,8 @@ public interface ICustomRole
ModdedRoleTeams Team { get; }
int MaxPlayers => 15;
LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Empty;
LoadableAsset<Sprite> Icon => MiraAssets.Empty;
@@ -53,7 +55,7 @@ public interface ICustomRole
RoleTypes GhostRole => Team == ModdedRoleTeams.Crewmate ? RoleTypes.CrewmateGhost : RoleTypes.ImpostorGhost;
MiraPluginInfo ParentMod => CustomRoleManager.FindParentMod(this);
/// <summary>
/// Runs on the PlayerControl FixedUpdate method for any player with this role
/// </summary>
@@ -76,7 +78,7 @@ public interface ICustomRole
var taskStringBuilder = Helpers.CreateForRole(this);
return taskStringBuilder;
}
bool IsModifierApplicable(BaseModifier modifier)
{
return true;
@@ -86,7 +88,7 @@ public interface ICustomRole
{
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)));
}
}
+6 -1
View File
@@ -1,4 +1,6 @@
namespace MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Utilities.Assets;
public static class MiraAssets
{
@@ -9,4 +11,7 @@ public static class MiraAssets
public static readonly LoadableResourceAsset Cog = new("MiraAPI.Resources.Cog.png");
public static readonly LoadableResourceAsset Checkmark = new("MiraAPI.Resources.Checkmark.png");
public static readonly LoadableResourceAsset CheckmarkBox = new("MiraAPI.Resources.CheckMarkBox.png");
public static readonly Color32 AcceptedTeal = new Color32(43, 233, 198, 255);
}
+72 -68
View File
@@ -1,62 +1,62 @@
#nullable enable
using System;
using System.Collections.Generic;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using Reactor.Utilities;
using System.Linq;
using MiraAPI.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
namespace MiraAPI.Utilities;
public static class Extensions
{
public static bool IsStatic(this Type type)
public static bool IsStatic(this System.Type type)
{
return type is { IsClass: true, IsAbstract: true, IsSealed: true };
}
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);
}
public static string? Truncate(this string? value, int maxLength, string truncationSuffix = "…")
{
return value?.Length > maxLength
? value[..maxLength] + truncationSuffix
: value;
}
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());
@@ -64,16 +64,29 @@ public static class Extensions
return chunks;
}
public static bool IsCustom(this OptionBehaviour optionBehaviour)
{
return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour));
}
public static readonly Dictionary<PlayerControl, ModifierComponent> ModifierComponents = new();
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;
}
public static List<T> Randomize<T>(this List<T> list)
@@ -89,16 +102,40 @@ public static class Extensions
return randomizedList;
}
public static T? GetModifier<T>(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent()?.ActiveModifiers.Find(x => x is T) as T;
}
public static bool HasModifier<T>(this PlayerControl? player) where T : BaseModifier
{
return player?.GetModifierComponent() != null && player.GetModifierComponent()!.ActiveModifiers.Exists(x => x is T);
}
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
[MethodRpc((uint)MiraRpc.RemoveModifier)]
public static void RpcRemoveModifier(this PlayerControl target, uint modifierId)
{
target.GetModifierComponent()?.RemoveModifier(modifierId);
}
[MethodRpc((uint)MiraRpc.AddModifier)]
public static BaseModifier? RpcAddModifier(this PlayerControl target, uint modifierId)
{
if (ModifierManager.IdToTypeModifiers.TryGetValue(modifierId, out var type))
{
return target.GetModifierComponent()?.AddModifier(type);
}
Logger<MiraApiPlugin>.Error($"Cannot add modifier with id {modifierId} because it is not registered.");
return null;
}
public static void RpcAddModifier<T>(this PlayerControl player) where T : BaseModifier
{
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
{
@@ -106,10 +143,10 @@ public static class Extensions
return;
}
ModifierComponent.RpcAddModifier(player, id);
player.RpcAddModifier(id);
}
public static void RemoveModifier<T>(this PlayerControl player) where T : BaseModifier
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
{
@@ -117,21 +154,21 @@ public static class Extensions
return;
}
ModifierComponent.RpcRemoveModifier(player, id);
player.RpcAddModifier(id);
}
public static Color DarkenColor(this Color color)
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);
return new Color(color.r - amount, color.g - amount, color.b - amount);
}
public static Color GetAlternateColor(this Color color)
public static Color GetAlternateColor(this Color color, float amount = 0.45f)
{
return color.IsColorDark() ? LightenColor(color) : DarkenColor(color);
return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount);
}
public static Color LightenColor(this Color color)
public static Color LightenColor(this Color color, float amount = 0.45f)
{
return new Color(color.r + 0.45f, color.g + 0.45f, color.b + 0.45f);
return new Color(color.r + amount, color.g + amount, color.b + amount);
}
public static bool IsColorDark(this Color color)
@@ -139,60 +176,27 @@ public static class Extensions
return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f };
}
public static DeadBody? NearestDeadBody(this PlayerControl playerControl, float radius)
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).FirstOrDefault(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
public static T? GetNearestObjectOfType<T>(this PlayerControl playerControl, float radius, string? colliderTag = null, System.Func<T, bool>? 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).FirstOrDefault(predicate ?? (component => component));
}
public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance)
public static PlayerControl? GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance, bool ignoreColliders = false)
{
PlayerControl? result = null;
if (!ShipStatus.Instance)
{
return null;
}
var truePosition = playerControl.GetTruePosition();
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 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.First();
}
}
+103 -4
View File
@@ -1,8 +1,10 @@
using MiraAPI.Roles;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Utilities;
@@ -14,6 +16,102 @@ public static class Helpers
return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos));
}
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();
}
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();
}
public static List<PlayerControl> GetClosestPlayersInCircle(Vector2 source, float radius, bool ignoreColliders = true)
{
List<PlayerControl> newList = GetNearestObjectsOfType<PlayerControl>(source, radius);
if (ignoreColliders)
{
List<PlayerControl> filteredList = new List<PlayerControl>();
foreach (var player in newList)
{
Vector2 vector = player.GetTruePosition() - source;
float magnitude = vector.magnitude;
if (!PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask))
{
filteredList.Add(player);
}
}
return filteredList;
}
return newList;
}
public static List<PlayerControl> GetClosestPlayers(PlayerControl source, float distance = 2f, bool ignoreColliders = true, bool ignoreSource = true)
{
if (!ShipStatus.Instance)
{
return null;
}
Vector2 myPos = source.GetTruePosition();
List<PlayerControl> players = GetClosestPlayers(myPos, distance, ignoreColliders);
return ignoreSource ? players.Where(plr => plr.PlayerId != source.PlayerId).ToList() : players;
}
public static List<PlayerControl> GetClosestPlayers(Vector2 source, float distance = 2f, bool ignoreColliders = true)
{
if (!ShipStatus.Instance)
{
return null;
}
List<PlayerControl> outputList = new List<PlayerControl>();
outputList.Clear();
List<NetworkedPlayerInfo> allPlayers = GameData.Instance.AllPlayers.ToArray().ToList();
for (int i = 0; i < allPlayers.Count; i++)
{
NetworkedPlayerInfo networkedPlayerInfo = allPlayers[i];
PlayerControl @object = networkedPlayerInfo.Object;
if (@object && @object.Collider.enabled)
{
Vector2 vector = @object.GetTruePosition() - source;
float magnitude = vector.magnitude;
if (magnitude <= distance && (ignoreColliders || !PhysicsHelpers.AnyNonTriggersBetween(source, vector.normalized, magnitude, Constants.ShipAndObjectsMask)))
{
outputList.Add(@object);
}
}
}
outputList.Sort(delegate (PlayerControl a, PlayerControl b)
{
float magnitude2 = (a.GetTruePosition() - source).magnitude;
float magnitude3 = (b.GetTruePosition() - source).magnitude;
if (magnitude2 > magnitude3)
{
return 1;
}
if (magnitude2 < magnitude3)
{
return -1;
}
return 0;
});
return outputList;
}
public static TextMeshPro CreateTextLabel(string name, Transform parent,
AspectPosition.EdgeAlignments alignment, Vector3 distance, float fontSize = 2f,
TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
@@ -46,13 +144,14 @@ TextAlignmentOptions textAlignment = TextAlignmentOptions.Center)
return Object.FindObjectsOfType<DeadBody>().FirstOrDefault(body => body.ParentId == id);
}
public static string GetSuffix(NumberSuffixes suffix)
public static string GetSuffix(MiraNumberSuffixes suffix)
{
return suffix switch
{
NumberSuffixes.None => string.Empty,
NumberSuffixes.Multiplier => "x",
NumberSuffixes.Seconds => "s",
MiraNumberSuffixes.None => string.Empty,
MiraNumberSuffixes.Multiplier => "x",
MiraNumberSuffixes.Seconds => "s",
MiraNumberSuffixes.Percent => "%",
_ => string.Empty
};
}
+8
View File
@@ -0,0 +1,8 @@
namespace MiraAPI.Utilities;
public enum MiraNumberSuffixes
{
None,
Multiplier,
Seconds,
Percent
}
+11
View File
@@ -57,6 +57,8 @@ Roles are very simple in Mira API. There are 3 things you need to do to create a
2. Implement the `ICustomRole` interface from Mira API.
3. Add the `[RegisterCustomRole]` attribute to the class.
**Disclaimer: Make sure your plugin class has the following attribute `[ReactorModFlags(ModFlags.RequireOnAllClients)]` or else your roles will not register correctly.**
Note: For step 1, if you are making neutral roles, choose either `CrewmateRole` or `ImpostorRole` as the base depending on if it can kill or not!
Mira API handles everything else, from adding the proper options to the settings menu, to managing the role assignment at the start of the game. There are no extra steps on the developer's part.
@@ -178,6 +180,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:
+16
View File
@@ -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
}
}
}
+219
View File
@@ -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>