Compare commits

...
13 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
31 changed files with 937 additions and 224 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>
@@ -18,7 +18,7 @@ public class FreezeButton : CustomActionButton<PlayerControl>
protected override void OnClick()
{
Target.AddModifier<FreezeModifier>();
Target?.RpcAddModifier<FreezeModifier>();
}
public override PlayerControl GetTarget()
+1 -1
View File
@@ -41,7 +41,7 @@ public class MeetingButton : CustomActionButton
if (UsesLeft == 0 && PlayerControl.LocalPlayer.HasModifier<CaptainModifier>())
{
PlayerControl.LocalPlayer.RemoveModifier<CaptainModifier>();
PlayerControl.LocalPlayer.RpcRemoveModifier<CaptainModifier>();
}
}
}
@@ -19,7 +19,7 @@ public class TeleportButton : CustomActionButton
public override int MaxUses => 0;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
public static bool IsZoom { get; private set; }
public override bool Enabled(RoleBehaviour role)
+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>
+6 -6
View File
@@ -22,13 +22,13 @@ public class ExampleOptions : AbstractOptionGroup
[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
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

+1 -1
View File
@@ -13,5 +13,5 @@ public class FreezerRole : ImpostorRole, ICustomRole
public Color RoleColor => Palette.Blue;
public ModdedRoleTeams Team => ModdedRoleTeams.Impostor;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
public int MaxPlayers => 2;
}
@@ -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;
}
+15 -1
View File
@@ -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,7 +137,13 @@ 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;
@@ -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);
}
+62 -8
View File
@@ -1,5 +1,4 @@
#nullable enable
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
@@ -55,17 +54,17 @@ 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.
@@ -135,6 +134,61 @@ public abstract class CustomActionButton
Button?.OverrideText(name);
}
/// <summary>
/// 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>
+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.2</Version>
<Description>Simple and easy to use Among Us modding API.</Description>
<Authors>Angxl, XtraCube</Authors>
<Company>All Of Us</Company>
<PackageLicenseExpression>LGPL-2.1-or-later</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<PackageId>AllOfUs.MiraAPI</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
@@ -17,27 +12,9 @@
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Checkmark.png" />
<EmbeddedResource Include="Resources\CheckMarkBox.png" />
<EmbeddedResource Include="Resources\Cog.png" />
<EmbeddedResource Include="Resources\CogActive.png" />
<EmbeddedResource Include="Resources\NextButton.png" />
<EmbeddedResource Include="Resources\NextButtonActive.png" />
<EmbeddedResource Include="Resources\Empty.png" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.1" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.8.13" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" />
</Target>
<Import Project="../AmongUs.props" />
</Project>
+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;
}
}
+8 -2
View File
@@ -106,7 +106,7 @@ public static class ModifierManager
}
shuffledModifiers.RemoveAt(0);
ModifierComponent.RpcAddModifier(plr, id);
plr.RpcAddModifier(id);
}
}
@@ -152,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);
}
}
}
+3 -2
View File
@@ -1,4 +1,5 @@
using Reactor.Utilities;
using MiraAPI.Utilities;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Modifiers.Types;
@@ -32,7 +33,7 @@ public abstract class TimedModifier : BaseModifier
if (RemoveOnComplete)
{
ModifierComponent.RpcRemoveModifier(Player, ModifierId);
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,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;
@@ -224,7 +224,7 @@ public static class RoleSettingMenuPatches
__instance.roleScreenshot.drawMode = SpriteDrawMode.Sliced;
__instance.roleHeaderSprite.color = customRole.RoleColor;
__instance.roleHeaderText.color = customRole.RoleColor.GetAlternateColor();
var bg = __instance.AdvancedRolesSettings.transform.Find("Background");
bg.localPosition = new Vector3(1.4041f, -7.08f, 0);
bg.GetComponent<SpriteRenderer>().size = new Vector2(89.4628f, 100);
@@ -237,7 +237,7 @@ public static class RoleSettingMenuPatches
{
continue;
}
optionBehaviour.OnValueChanged = new Action<OptionBehaviour>(__instance.ValueChanged);
if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost)
{
@@ -263,7 +263,7 @@ public static class RoleSettingMenuPatches
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
roleOptionSetting.titleText.color = customRole.RoleColor.GetAlternateColor();
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
if (GameSettingMenuPatches.SelectedMod is null ||
GameSettingMenuPatches.SelectedMod.Options.Any(x => x.AdvancedRole != null && x.AdvancedRole.IsInstanceOfType(role)))
{
+29 -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
@@ -206,6 +216,12 @@ public static class CustomRoleManager
}
}
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)));
}
}
+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();
}
}
+98
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)
+9
View File
@@ -180,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>