Compare commits

...
96 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
XtraCube d54c47c835 oops 2024-08-27 18:24:51 -04:00
XtraCube 5d4f9eb06a Create main.yaml 2024-08-27 18:24:28 -04:00
XtraCube d153f10f0b Create build.cake 2024-08-27 18:23:54 -04:00
XtraCube f8af852286 Update README.md 2024-08-27 18:22:19 -04:00
XtraCube 9fb25542e7 all fixes except TMP mask 2024-08-27 18:12:20 -04:00
ang-xd 8a9300425a Semi broken task scroll 2024-08-27 17:19:50 -04:00
XtraCube 4277193aed we forgot scrolling colors 2024-08-27 16:18:15 -04:00
XtraCube a9015a3535 nugget preparations 2024-08-27 16:07:24 -04:00
XtraCube 67f0513605 fix 2024-08-27 15:56:12 -04:00
XtraCube 9c751bcd39 prep for release 2024-08-27 15:48:19 -04:00
XtraCube 10b8999f3e make colors make sense 2024-08-27 15:38:08 -04:00
XtraCube 76c39d9bdc final touches and fixes 2024-08-27 15:32:26 -04:00
XtraCube 016d21075f add custom color docs to readme 2024-08-27 15:07:20 -04:00
XtraCube 36a419d721 Merge pull request #6 from All-Of-Us-Mods/modifiers
Modifiers
2024-08-27 14:59:53 -04:00
XtraCube 87a1be7410 modifiers README 2024-08-27 14:59:16 -04:00
XtraCube 892c5c7c70 final touches 2024-08-27 14:46:53 -04:00
XtraCube 625cd2ca4b Merge branch 'refs/heads/master' into modifiers
# Conflicts:
#	MiraAPI.Example/Buttons/ExampleButton.cs
#	MiraAPI.Example/ExampleRole.cs
#	MiraAPI/GameOptions/OptionTypes/ModdedOption.cs
#	MiraAPI/Hud/CustomActionButton.cs
#	MiraAPI/MiraApiPlugin.cs
#	MiraAPI/Patches/Options/RoleSettingMenuPatches.cs
#	MiraAPI/Patches/PlayerControlPatches.cs
#	MiraAPI/PluginLoading/MiraPluginManager.cs
#	MiraAPI/Roles/ICustomRole.cs
#	MiraAPI/Utilities/Extensions.cs
2024-08-27 14:43:05 -04:00
XtraCube 39851c6269 hmm 2024-08-27 14:33:33 -04:00
XtraCube 013c94d064 Merge pull request #5 from All-Of-Us-Mods/collapsible-options
Collapsible options, and fixes
2024-08-27 14:30:44 -04:00
XtraCube 2dadf08df4 a todo 2024-08-27 14:30:27 -04:00
XtraCube 74592b2558 oops role ID had issues 2024-08-27 14:29:06 -04:00
XtraCube 157c5af280 more readme 2024-08-27 14:05:04 -04:00
XtraCube 1cc41052e3 Merge branch 'refs/heads/master' into collapsible-options 2024-08-27 13:58:25 -04:00
ang-xd 8a8fb7ced4 Collapsible options, and fixes 2024-08-27 13:57:38 -04:00
XtraCube 99d5efca1a Merge pull request #4 from All-Of-Us-Mods/sequential-role-ids
sequential role IDs
2024-08-27 13:56:32 -04:00
XtraCube 37aee8712e readme update 2024-08-27 13:54:40 -04:00
XtraCube f39580e858 fixes 2024-08-27 13:26:32 -04:00
XtraCube 201826dc62 Merge branch 'refs/heads/master' into sequential-role-ids
# Conflicts:
#	MiraAPI/PluginLoading/MiraPluginInfo.cs
#	MiraAPI/PluginLoading/MiraPluginManager.cs
2024-08-27 13:23:38 -04:00
XtraCube 4cd8aa0bd7 advanced role settings in view tab 2024-08-27 13:18:38 -04:00
XtraCube cd1529b4bf QOL in view tab 2024-08-27 12:51:00 -04:00
XtraCube 564f524dd6 lobby view pane almost done 2024-08-27 12:45:22 -04:00
XtraCube 5e9c592aab prep for view pane 2024-08-27 12:44:53 -04:00
XtraCube d96b146e59 set neutral header color for uniformity 2024-08-27 12:44:41 -04:00
XtraCube a2b1e9ddbf increase contrast on AlternateColors 2024-08-27 12:44:28 -04:00
ang-xd dfa5591188 modifiers UI 2024-08-26 21:28:34 -04:00
ang-xd a88646465b GameModifiers, and fixes 2024-08-26 20:02:05 -04:00
XtraCube f4f2cf9182 braces 2024-08-26 16:27:00 -04:00
XtraCube c77664936f add button location property 2024-08-26 11:17:53 -04:00
XtraCube 55b7174be2 fix palette manager 2024-08-26 11:10:39 -04:00
XtraCube af471f0232 xd 2024-08-26 10:45:42 -04:00
XtraCube e4e3a10ed8 . 2024-08-26 10:41:36 -04:00
XtraCube d0bbb191c5 rename to palette 2024-08-26 10:38:35 -04:00
XtraCube 6de53fb761 custom colors API stuff 2024-08-26 09:57:46 -04:00
XtraCube 0e39612325 add docs to target button 2024-08-26 09:57:35 -04:00
XtraCube b62d1b6565 targeting action button 2024-08-25 21:51:22 -04:00
XtraCube 70320d7c19 fixing a possible version not having commit hash attached to it case 2024-08-25 09:45:57 -04:00
XtraCube 2eb2567db9 update to 2.3.1 and use ReactorCredits 2024-08-24 23:22:21 -04:00
XtraCube 3226d9fea5 small fixes 2024-08-24 16:52:20 -04:00
XtraCube a9ff1c7f63 networking modifiers 2024-08-24 13:36:49 -04:00
ang-xd 9b173a4298 Updates to Modifiers 2024-08-23 20:30:34 -04:00
XtraCube 26284852c9 fix merge issues 2024-08-23 15:48:16 -04:00
XtraCube 140d9ed353 Merge remote-tracking branch 'origin/modifiers' into modifiers
# Conflicts:
#	MiraAPI/Modifiers/BaseModifier.cs
#	MiraAPI/Modifiers/ModifierManager.cs
#	MiraAPI/Utilities/Extensions.cs
2024-08-23 15:45:47 -04:00
XtraCube 81f9fa728b rework how modifiers exist 2024-08-23 15:45:18 -04:00
ang-xd 5c24488049 Idk 2024-08-23 15:18:21 -04:00
XtraCube a0a2e9a6ab sequential role IDs 2024-08-22 18:48:38 -04:00
ang-xd a077cabea1 Modifiers base 2024-08-20 21:42:35 -04:00
96 changed files with 3310 additions and 720 deletions

No files matched your search

+51
View File
@@ -0,0 +1,51 @@
name: CI
on: ["workflow_dispatch", "push", "pull_request"]
jobs:
build:
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: 8.0.x
- name: Run the Cake script
uses: cake-build/cake-action@v2
with:
verbosity: Diagnostic
- uses: actions/upload-artifact@v3
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>
-26
View File
@@ -1,26 +0,0 @@
using MiraAPI.Hud;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Buttons;
[RegisterButton]
public class ExampleButton : CustomActionButton
{
public override string Name => "Example Button";
public override float Cooldown => 5f;
public override float EffectDuration => 0f;
public override int MaxUses => 5;
public override LoadableAsset<Sprite> Sprite { get; } = ExampleAssets.ExampleButton;
protected override void OnClick()
{
Logger<ExamplePlugin>.Info("Example button clicked!");
}
public override bool Enabled(RoleBehaviour role)
{
return true;
}
}
@@ -0,0 +1,44 @@
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.Freezer;
[RegisterButton]
public class FreezeButton : CustomActionButton<PlayerControl>
{
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()
{
Target?.RpcAddModifier<FreezeModifier>();
}
public override PlayerControl GetTarget()
{
return PlayerControl.LocalPlayer.Data.Role.FindClosestTarget();
}
public override void SetOutline(bool active)
{
Target?.cosmetics.SetOutline(active, new Il2CppSystem.Nullable<Color>(Palette.Blue));
}
public override bool IsTargetValid(PlayerControl target)
{
return true;
}
public override bool Enabled(RoleBehaviour role)
{
return role is FreezerRole;
}
}
+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");
}
+14
View File
@@ -0,0 +1,14 @@
using MiraAPI.Colors;
using UnityEngine;
namespace MiraAPI.Example;
[RegisterCustomColors]
public static class ExampleColors
{
public static CustomColor Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f));
public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f));
public static CustomColor Gold { get; } = new("Gold", new Color(1.0f, 0.84f, 0.0f));
}
+3 -1
View File
@@ -9,7 +9,7 @@ using Reactor.Networking.Attributes;
namespace MiraAPI.Example;
[BepInAutoPlugin("mira.example", "MiraAPIExample")]
[BepInAutoPlugin("mira.example", "MiraExampleMod")]
[BepInProcess("Among Us.exe")]
[BepInDependency(ReactorPlugin.Id)]
[BepInDependency(MiraApiPlugin.Id)]
@@ -17,9 +17,11 @@ namespace MiraAPI.Example;
public partial class ExamplePlugin : BasePlugin, IMiraPlugin
{
public Harmony Harmony { get; } = new(Id);
public string OptionsTitleText => "Mira API\nExample Mod";
public ConfigFile GetConfigFile() => Config;
public override void Load()
{
Harmony.PatchAll();
AddComponent<MiraDebugWindow>();
}
}
+4 -22
View File
@@ -1,26 +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.0" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="Iced" Version="1.21.0" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.8.13" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
@@ -29,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>
+31
View File
@@ -0,0 +1,31 @@
using MiraAPI.Example.Modifiers;
using MiraAPI.Utilities;
using Reactor.Utilities.Attributes;
using Reactor.Utilities.ImGui;
using System;
using UnityEngine;
namespace MiraAPI.Example;
[RegisterInIl2Cpp]
public class MiraDebugWindow(IntPtr ptr) : MonoBehaviour(ptr)
{
public readonly DragWindow DebuggingWindow = new(new Rect(10, 10, 0, 0), "MIRA API DEBUGGING", () =>
{
if (GUILayout.Button("Test modifier"))
{
//PlayerControl.LocalPlayer.AddModifier<ModifierTimerExample>();
}
if (GUILayout.Button("Remove modifier"))
{
//PlayerControl.LocalPlayer.RemoveModifier<ModifierTimerExample>();
}
})
{
Enabled = true
};
public void OnGUI()
{
DebuggingWindow.OnGUI();
}
}
-8
View File
@@ -1,8 +0,0 @@
namespace MiraAPI.Example;
public class ModifierExample
{
//name, role specific, etc
// mono behaviour stuff here (ModdedPlayerModifier inherits monobehaviour)
}
@@ -0,0 +1,19 @@
using MiraAPI.Modifiers;
using MiraAPI.Modifiers.Types;
namespace MiraAPI.Example.Modifiers;
[RegisterModifier]
public class CaptainModifier : GameModifier
{
public override string ModifierName => "Captain";
public override int GetAmountPerGame()
{
return 1;
}
public override int GetAssignmentChance()
{
return 100;
}
}
@@ -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;
}
}
}
+17 -16
View File
@@ -1,33 +1,34 @@
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Utilities;
using UnityEngine;
namespace MiraAPI.Example.Options;
public class ExampleOptions : AbstractOptionGroup
{
public override string GroupName => "General Group";
public override Color GroupColor => Color.red;
public override string GroupName => "Example Options 1";
public override Color GroupColor => Color.green;
[ModdedToggleOption("Use Thing")]
public bool UseThing { get; set; } = false;
[ModdedToggleOption("Toggle Opt 1")]
public bool ToggleOpt { get; set; } = false;
[ModdedToggleOption("Use another thing")]
public bool UseAnotherThing { get; set; } = true;
[ModdedToggleOption("Toggle Opt 2")]
public bool ToggleOpt2 { get; set; } = true;
[ModdedNumberOption("Sussy level", min: 0, max: 10)]
public float SussyLevel { get; set; } = 4f;
[ModdedNumberOption("Number Opt", min: 0, max: 10, suffixType: MiraNumberSuffixes.Percent)]
public float NumberOpt { get; set; } = 4f;
[ModdedStringOption("Sus choices", ["hello", "hello 2", "hello 3"])]
public int SusChoices { get; set; } = 2;
[ModdedStringOption("String Opt", ["Hello", "Hey", "Bye"])]
public int StringOpt { get; set; } = 2;
[ModdedEnumOption("Enum test", typeof(TestEnum))]
public TestEnum EnumTest { get; set; } = TestEnum.Bye;
[ModdedEnumOption("Best API", typeof(BestApiEnum), ["Mira API", "Mitochondria", "Reactor"])]
public BestApiEnum EnumOpt { get; set; } = BestApiEnum.MiraAPI;
}
public enum TestEnum
public enum BestApiEnum
{
Hello,
Hi,
Bye
MiraAPI,
Mitochondria,
Reactor
}
+16 -16
View File
@@ -5,23 +5,23 @@ namespace MiraAPI.Example.Options;
public class ExampleOptions2 : AbstractOptionGroup
{
public override string GroupName => "Example 2";
public ModdedToggleOption UseThing { get; } = new("Use Thingy 5", false);
public ModdedToggleOption UseThing2 { get; } = new("Use Thingy 6", false);
public ModdedStringOption Sussy { get; } = new("Sussy", 0, ["Sussy 1", "Sussy 2", "Sussy 3"]);
public ModdedEnumOption SussyEnum { get; } = new("Sussy Enum", 0, typeof(SussyEnum));
public ModdedNumberOption YeezyLevel { get; } = new("Yeezy Level", 50, 0, 100, 25, NumberSuffixes.Multiplier);
public override string GroupName => "Example Options 2";
public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false);
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
{
Visible = () => OptionGroupSingleton<ExampleOptions2>.Instance.ToggleOpt1.Value
};
public ModdedStringOption StringOpt { get; } = new("String Opt", 0, ["Choice 1", "Choice 2", "Choice 3"]);
public ModdedEnumOption EnumOpt { get; } = new("Enum Opt", 0, typeof(TestingEnum));
}
public enum SussyEnum
public enum TestingEnum
{
Sussy,
Baka,
Imposter,
Kanye
Happy,
Sad,
Neutral
}
@@ -1,7 +1,7 @@
using System;
using MiraAPI.Example.Roles;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using System;
namespace MiraAPI.Example.Options.Roles;
@@ -9,8 +9,8 @@ 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 System;
using MiraAPI.Example.Roles;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
namespace MiraAPI.Example.Options.Roles;
public class CustomRoleSettings : AbstractOptionGroup
{
public override string GroupName => "Custom Role";
public override Type AdvancedRole => typeof(CustomRole);
[ModdedNumberOption("Testing level", min: 3, max: 9)] public float TestingLevel { get; set; } = 4;
}
@@ -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

-17
View File
@@ -1,17 +0,0 @@
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole2 : CrewmateRole, ICustomRole
{
public string RoleName => "fortnite defender";
public string RoleLongDescription => "ok so your objective is to save everyone from the fortnite killer";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.AcceptedGreen;
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
}
@@ -5,13 +5,13 @@ using UnityEngine;
namespace MiraAPI.Example.Roles;
[RegisterCustomRole]
public class CustomRole : ImpostorRole, ICustomRole
public class FreezerRole : ImpostorRole, ICustomRole
{
public string RoleName => "fortnite killer";
public string RoleLongDescription => "ok so your objective is to eliminate everyone with your fortnite tactical shotgun, good luck";
public string 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 CustomRole3 : CrewmateRole, ICustomRole
public class TeleporterRole : CrewmateRole, ICustomRole
{
public string RoleName => "fortnite helper";
public string RoleLongDescription => "ok so your objective is to help";
public string RoleName => "Teleporter";
public string RoleLongDescription => "Zoom out and teleport across the map!";
public string RoleDescription => RoleLongDescription;
public Color RoleColor => Palette.Brown;
public Color RoleColor => new Color32(221, 176, 152, 255);
public ModdedRoleTeams Team => ModdedRoleTeams.Crewmate;
public LoadableAsset<Sprite> OptionsScreenshot => ExampleAssets.Banner;
+23
View File
@@ -0,0 +1,23 @@
using MiraAPI.Utilities;
using Reactor.Localization.Utilities;
using UnityEngine;
namespace MiraAPI.Colors;
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
{
public Color32 MainColor { get; } = mainColor;
public Color32 ShadowColor { get; } = shadowColor;
public StringNames Name { get; } = name;
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
{
}
public CustomColor(string name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
{
}
public CustomColor(string name, Color32 mainColor, Color32 shadowColor) : this(CustomStringName.CreateAndRegister(name), mainColor, shadowColor)
{
}
}
+21
View File
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
namespace MiraAPI.Colors;
internal static class PaletteManager
{
public static readonly List<CustomColor> CustomColors = [];
public static void RegisterAllColors()
{
var colors = CustomColors.Select(x => x.MainColor);
var shadowColors = CustomColors.Select(x => x.ShadowColor);
var stringNames = CustomColors.Select(x => x.Name);
Palette.PlayerColors = Palette.PlayerColors.ToArray().AddRangeToArray(colors.ToArray());
Palette.ShadowColors = Palette.ShadowColors.ToArray().AddRangeToArray(shadowColors.ToArray());
Palette.ColorNames = Palette.ColorNames.ToArray().AddRangeToArray(stringNames.ToArray());
}
}
@@ -0,0 +1,6 @@
using System;
namespace MiraAPI.Colors;
[AttributeUsage(AttributeTargets.Class)]
public class RegisterCustomColorsAttribute : Attribute;
+2 -2
View File
@@ -14,7 +14,7 @@ public static class CustomGameModeManager
/// <summary>
/// List of registered gamemodes
/// </summary>
public static readonly Dictionary<int, CustomGameMode> GameModes = [];
internal static readonly Dictionary<int, CustomGameMode> GameModes = [];
public static bool IsDefault()
{
@@ -24,7 +24,7 @@ public static class CustomGameModeManager
/// <summary>
/// Current gamemode
/// </summary>
public static CustomGameMode? ActiveMode = new DefaultMode();
public static CustomGameMode? ActiveMode { get; internal set; } = new DefaultMode();
/// <summary>
/// Set current gamemode
+8 -4
View File
@@ -7,24 +7,28 @@ namespace MiraAPI.GameOptions;
public abstract class AbstractOptionGroup
{
public List<IModdedOption> Options { get; } = [];
/// <summary>
/// The name of the group. Visible in options menu.
/// </summary>
public abstract string GroupName { get; }
/// <summary>
/// A function that returns whether the group should be visible or not.
/// </summary>
public virtual Func<bool> GroupVisible => () => true;
/// <summary>
/// The group color. This is used to color the group in the options menu.
/// </summary>
public virtual Color GroupColor => Color.clear;
/// <summary>
/// The role the group is associated with. This is used for the advanced role options menu.
/// </summary>
public virtual Type AdvancedRole => null;
internal bool AllOptionsHidden { get; set; }
internal CategoryHeaderMasked Header { get; set; }
}
@@ -4,24 +4,24 @@ 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;
}
public override void SetValue(object value)
{
ModdedEnumOption opt = HolderOption as ModdedEnumOption;
var opt = HolderOption as ModdedEnumOption;
opt.SetValue((int)value);
}
public override object GetValue()
{
ModdedEnumOption opt = HolderOption as ModdedEnumOption;
var opt = HolderOption as ModdedEnumOption;
return Enum.ToObject(enumType, opt.Value);
}
}
@@ -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)
@@ -22,13 +23,13 @@ public class ModdedNumberOptionAttribute(
public override void SetValue(object value)
{
ModdedNumberOption toggleOpt = HolderOption as ModdedNumberOption;
var toggleOpt = HolderOption as ModdedNumberOption;
toggleOpt.SetValue((float)value);
}
public override object GetValue()
{
ModdedNumberOption toggleOpt = HolderOption as ModdedNumberOption;
var toggleOpt = HolderOption as ModdedNumberOption;
return toggleOpt.Value;
}
}
@@ -6,7 +6,7 @@ namespace MiraAPI.GameOptions.Attributes;
[AttributeUsage(AttributeTargets.Property)]
public abstract class ModdedOptionAttribute(string title, Type roleType = null) : Attribute
{
public IModdedOption HolderOption { get; set; }
public IModdedOption HolderOption { get; internal set; }
public string Title { get; private set; } = title;
@@ -15,13 +15,13 @@ public class ModdedStringOptionAttribute(string title, string[] values, Type rol
public override void SetValue(object value)
{
ModdedStringOption opt = HolderOption as ModdedStringOption;
var opt = HolderOption as ModdedStringOption;
opt.SetValue((int)value);
}
public override object GetValue()
{
ModdedStringOption opt = HolderOption as ModdedStringOption;
var opt = HolderOption as ModdedStringOption;
return opt.Value;
}
}
@@ -14,13 +14,13 @@ public class ModdedToggleOptionAttribute(string title, Type roleType = null) : M
public override void SetValue(object value)
{
ModdedToggleOption toggleOpt = HolderOption as ModdedToggleOption;
var toggleOpt = HolderOption as ModdedToggleOption;
toggleOpt.SetValue((bool)value);
}
public override object GetValue()
{
ModdedToggleOption toggleOpt = HolderOption as ModdedToggleOption;
var toggleOpt = HolderOption as ModdedToggleOption;
return toggleOpt.Value;
}
}
+1
View File
@@ -19,6 +19,7 @@ public interface IModdedOption
public ConfigDefinition ConfigDefinition { get; }
public void ValueChanged(OptionBehaviour optionBehaviour);
public OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
public abstract float GetFloatData();
public NetData GetNetData();
public void HandleNetData(byte[] data);
}
+29 -27
View File
@@ -7,19 +7,20 @@ using System.Linq;
using System.Reflection;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
namespace MiraAPI.GameOptions;
public class ModdedOptionsManager
public static class ModdedOptionsManager
{
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = new();
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = new();
public static readonly Dictionary<uint, IModdedOption> ModdedOptions = new();
public static readonly List<AbstractOptionGroup> Groups = [];
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = new();
internal static readonly List<AbstractOptionGroup> Groups = [];
public static uint NextId => _nextId++;
internal static uint NextId => _nextId++;
private static uint _nextId = 1;
internal static bool RegisterGroup(Type type, MiraPluginInfo pluginInfo)
@@ -109,33 +110,24 @@ public class ModdedOptionsManager
internal static void SyncAllOptions(int targetId=-1)
{
List<NetData> data = [];
var chunks = ModdedOptions.Values.Select(option => option.GetNetData()).ChunkNetData(1000);
var count = 0;
foreach (var netData in ModdedOptions.Values.Select(option => option.GetNetData()))
while (chunks.Count > 0)
{
data.Add(netData);
count += netData.GetLength();
if (count <= 1000)
{
continue;
}
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
data.Clear();
count = 0;
}
if (data.Count > 0)
{
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
Rpc<SyncOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, chunks.Dequeue());
}
}
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))
@@ -143,22 +135,32 @@ public class ModdedOptionsManager
continue;
}
Logger<MiraApiPlugin>.Error("Handling option " + option.Title);
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();
}
}
public static void PropertySetterPatch(MethodBase __originalMethod, object value)
{
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value;
var attribute = OptionAttributes.First(pair => pair.Key.GetSetMethod().Equals(__originalMethod)).Value;
attribute?.SetValue(value);
}
public static bool PropertyGetterPatch(MethodBase __originalMethod, ref object __result)
{
ModdedOptionAttribute attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value;
var attribute = OptionAttributes.First(pair => pair.Key.GetGetMethod().Equals(__originalMethod)).Value;
if (attribute == null)
{
@@ -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;
@@ -39,7 +42,12 @@ public class ModdedEnumOption : ModdedOption<int>
return stringOption;
}
public override float GetFloatData()
{
return Value;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
@@ -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,22 +49,27 @@ 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;
}
public override float GetFloatData()
{
return Value;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
+16 -15
View File
@@ -1,10 +1,10 @@
using MiraAPI.PluginLoading;
using BepInEx.Configuration;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using Reactor.Localization.Utilities;
using System;
using BepInEx.Configuration;
using MiraAPI.Networking;
using Reactor.Networking.Rpc;
using System;
using UnityEngine;
namespace MiraAPI.GameOptions.OptionTypes;
@@ -12,7 +12,7 @@ namespace MiraAPI.GameOptions.OptionTypes;
public abstract class ModdedOption<T> : IModdedOption
{
private IMiraPlugin _parentMod;
public uint Id { get; }
public BaseGameSetting Data { get; protected init; }
public IMiraPlugin ParentMod
@@ -26,14 +26,14 @@ public abstract class ModdedOption<T> : IModdedOption
}
}
public T Value { get; protected set; }
public T DefaultValue { get; init; }
public T DefaultValue { get; }
public Action<T> ChangedEvent { get; set; }
public string Title { get; }
public StringNames StringName { get; }
public Func<bool> Visible { get; set; }
public Type AdvancedRole { get; set; }
public OptionBehaviour OptionBehaviour { get; set; }
public ConfigDefinition ConfigDefinition => new ("Options", Title);
public ConfigDefinition ConfigDefinition => new("Options", Title);
protected ModdedOption(string title, T defaultValue, Type roleType)
{
@@ -66,20 +66,21 @@ 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 is not null &&
ParentMod.GetConfigFile().TryGetEntry<T>(ConfigDefinition, out var entry))
{
entry.Value = Value;
}
OnValueChanged(newValue);
}
public abstract float GetFloatData();
public abstract NetData GetNetData();
public abstract void HandleNetData(byte[] data);
public abstract void OnValueChanged(T newValue);
@@ -40,6 +40,11 @@ public class ModdedStringOption : ModdedOption<int>
return stringOption;
}
public override float GetFloatData()
{
return Value;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
@@ -32,6 +32,11 @@ public class ModdedToggleOption : ModdedOption<bool>
return toggleOption;
}
public override float GetFloatData()
{
return Value ? 1 : 0;
}
public override NetData GetNetData()
{
return new NetData(Id, BitConverter.GetBytes(Value));
+7
View File
@@ -0,0 +1,7 @@
namespace MiraAPI.Hud;
public enum ButtonLocation
{
BottomLeft,
BottomRight
}
+154 -29
View File
@@ -1,9 +1,14 @@
using MiraAPI.Utilities.Assets;
#nullable enable
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
namespace MiraAPI.Hud;
/// <summary>
/// Class for making custom action buttons. More customizable than the default Action/Ability buttons in the base game
/// </summary>
public abstract class CustomActionButton
{
/// <summary>
@@ -31,6 +36,11 @@ public abstract class CustomActionButton
/// </summary>
public abstract LoadableAsset<Sprite> Sprite { get; }
/// <summary>
/// The location of the button on the screen
/// </summary>
public virtual ButtonLocation Location => ButtonLocation.BottomLeft;
/// <summary>
/// Returns true if the button has an effect ability.
/// </summary>
@@ -44,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; }
protected ActionButton? Button { get; private set; }
internal void CreateButton(Transform parent)
{
if (Button)
@@ -85,31 +95,34 @@ 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>
/// <param name="sprite">The new sprite to override with</param>
public void OverrideSprite(Sprite sprite)
{
Button.graphic.sprite = sprite;
if (Button != null)
{
Button.graphic.sprite = sprite;
}
}
/// <summary>
@@ -118,17 +131,72 @@ public abstract class CustomActionButton
/// <param name="name">The new name to override with</param>
public void OverrideName(string name)
{
Button.OverrideText(name);
Button?.OverrideText(name);
}
/// <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();
@@ -137,10 +205,11 @@ public abstract class CustomActionButton
/// True means the button is active, false means the button is disabled.
/// </summary>
/// <param name="role">The role of the local player.</param>
public abstract bool Enabled(RoleBehaviour role);
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() { }
@@ -164,7 +233,7 @@ public abstract class CustomActionButton
/// <param name="role">Passed in from HudManager.SetHudActive, the current role of the player.</param>
public virtual void SetActive(bool visible, RoleBehaviour role)
{
Button.ToggleVisible(visible && Enabled(role));
Button?.ToggleVisible(visible && Enabled(role));
}
/// <summary>
@@ -172,7 +241,7 @@ public abstract class CustomActionButton
/// This method takes into account cooldowns, effects, and uses, before calling the <see cref="OnClick"/> method.
/// It can be overridden for custom behavior.
/// </summary>
public virtual void ClickHandler()
protected virtual void ClickHandler()
{
if (!CanUse())
{
@@ -182,11 +251,11 @@ public abstract class CustomActionButton
if (LimitedUses)
{
UsesLeft--;
Button.SetUsesRemaining(UsesLeft);
Button?.SetUsesRemaining(UsesLeft);
}
OnClick();
Button.SetDisabled();
Button?.SetDisabled();
if (HasEffect)
{
EffectActive = true;
@@ -219,15 +288,71 @@ public abstract class CustomActionButton
if (CanUse())
{
Button.SetEnabled();
Button?.SetEnabled();
}
else
{
Button.SetDisabled();
Button?.SetDisabled();
}
Button.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown);
Button?.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown);
FixedUpdate(playerControl);
}
}
/// <summary>
/// Custom action button that has a target object.
/// </summary>
/// <typeparam name="T">The type of the target object.</typeparam>
public abstract class CustomActionButton<T> : CustomActionButton where T : MonoBehaviour
{
/// <summary>
/// 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>
public virtual string? ColliderTag => null;
/// <summary>
/// Determines if the target object is valid.
/// </summary>
public virtual bool IsTargetValid(T? target)
{
return target;
}
/// <summary>
/// The method used to get the target object.
/// </summary>
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();
if (newTarget != Target)
{
SetOutline(false);
}
Target = IsTargetValid(newTarget) ? newTarget : null;
SetOutline(true);
return base.CanUse() && Target;
}
}
+2 -2
View File
@@ -5,9 +5,9 @@ using Reactor.Utilities;
namespace MiraAPI.Hud;
public static class CustomButtonManager
internal static class CustomButtonManager
{
public static readonly List<CustomActionButton> CustomButtons = [];
internal static readonly List<CustomActionButton> CustomButtons = [];
internal static void RegisterButton(Type buttonType)
{
+8 -24
View File
@@ -1,36 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>latest</LangVersion>
<DebugType>embedded</DebugType>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>dev</VersionSuffix>
<Description>Simple and easy to use Among Us modding API.</Description>
<Authors>Angxl, XtraCube</Authors>
<PackageId>AllOfUs.MiraAPI</PackageId>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<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" />
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Reactor" Version="2.3.0" />
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.697" Private="false" ExcludeAssets="runtime;native" />
<PackageReference Include="Iced" Version="1.21.0" />
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2024.8.13" PrivateAssets="all" />
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime">
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<EmbeddedResource Include="Resources\**\*.*" />
</ItemGroup>
<Target Name="Copy" AfterTargets="Build" Condition="'$(AmongUs)' != ''">
<Copy SourceFiles="$(TargetPath)" DestinationFolder="$(AmongUs)/BepInEx/plugins/" />
</Target>
<Import Project="../AmongUs.props" />
</Project>
+5 -20
View File
@@ -2,12 +2,11 @@
using BepInEx.Unity.IL2CPP;
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using Reactor;
using Reactor.Patches;
using System;
using System.Text;
using Reactor.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI;
@@ -20,30 +19,16 @@ public partial class MiraApiPlugin : BasePlugin
{
public Harmony Harmony { get; } = new(Id);
private static MiraPluginManager PluginManager { get; set; }
public static Color MiraColor = new Color32(238, 154, 112, 255);
public override void Load()
{
Harmony.PatchAll();
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
PluginManager = new MiraPluginManager();
PluginManager.Initialize();
ReactorVersionShower.TextUpdated += text =>
{
text.text = new StringBuilder($"{MiraColor.ToTextColor()}Mira API</color> ")
.Append(GetShortHashVersion(Version))
.Append('\n')
.Append(text.text)
.ToString();
};
}
private static string GetShortHashVersion(string version)
{
var index = version.IndexOf("+", StringComparison.Ordinal);
return index < 0 ? version : version[..(index + 3)];
}
}
+14
View File
@@ -0,0 +1,14 @@
namespace MiraAPI.Modifiers;
public abstract class BaseModifier
{
public abstract string ModifierName { get; }
public virtual bool HideOnUi => false;
public PlayerControl Player { get; internal set; }
public uint ModifierId { get; internal set; }
public virtual void OnActivate() { }
public virtual void OnDeactivate() { }
public virtual void Update() { }
public virtual void FixedUpdate() { }
public virtual void OnDeath() { }
public virtual bool CanVent() => Player.Data.Role.CanVent;
}
+190
View File
@@ -0,0 +1,190 @@
using MiraAPI.Modifiers.Types;
using MiraAPI.Utilities;
using Reactor.Utilities;
using Reactor.Utilities.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
namespace MiraAPI.Modifiers;
[RegisterInIl2Cpp]
public class ModifierComponent(IntPtr ptr) : MonoBehaviour(ptr)
{
public List<BaseModifier> ActiveModifiers { get; private set; }
public PlayerControl player;
private TextMeshPro _modifierText;
public void Start()
{
player = GetComponent<PlayerControl>();
ActiveModifiers = [];
if (!player.AmOwner)
{
return;
}
_modifierText = Helpers.CreateTextLabel("ModifierText", HudManager.Instance.transform, AspectPosition.EdgeAlignments.RightTop, new Vector3(10.1f, 3.5f, 0), textAlignment: TextAlignmentOptions.Right);
_modifierText.verticalAlignment = VerticalAlignmentOptions.Top;
}
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();
foreach (var mod in baseModifiers)
{
stringBuild.Append($"\n{mod.ModifierName}");
if (mod is TimedModifier timer)
{
stringBuild.Append($" <size=70%>({Math.Round(timer.Duration - timer.TimeRemaining, 0)}s/{timer.Duration}s)</size>");
}
}
_modifierText.text = $"<b><size=130%>Modifiers:</b></size>{stringBuild}";
}
else if (player.AmOwner && !baseModifiers.Any() && _modifierText.text != string.Empty)
{
_modifierText.text = string.Empty;
}
}
public void RemoveModifier(Type type)
{
var modifier = ActiveModifiers.Find(x => x.GetType() == type);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
return;
}
RemoveModifier(modifier);
}
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 BaseModifier AddModifier(BaseModifier modifier)
{
if (ActiveModifiers.Contains(modifier))
{
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {modifier.ModifierId}!");
return null;
}
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 null;
}
var modifier = (BaseModifier)Activator.CreateInstance(type);
if (modifier is null)
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {type.Name} because it is null.");
return null;
}
AddModifier(modifier);
return modifier;
}
public T AddModifier<T>() where T : BaseModifier
{
return AddModifier(typeof(T)) as T;
}
}
+182
View File
@@ -0,0 +1,182 @@
using MiraAPI.Modifiers.Types;
using MiraAPI.Networking;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using Reactor.Networking.Rpc;
using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using Reactor.Utilities;
using Random = System.Random;
namespace MiraAPI.Modifiers;
public static class ModifierManager
{
public static readonly Dictionary<uint, Type> IdToTypeModifiers = [];
public static readonly Dictionary<Type, uint> TypeToIdModifiers = [];
private static uint _nextId;
private static uint GetNextId()
{
_nextId++;
return _nextId;
}
internal static void RegisterModifier(Type modifierType)
{
if (!typeof(BaseModifier).IsAssignableFrom(modifierType))
{
return;
}
IdToTypeModifiers.Add(GetNextId(), modifierType);
TypeToIdModifiers.Add(modifierType, _nextId);
}
internal static void AssignModifiers(List<PlayerControl> plrs)
{
var rand = new Random();
List<uint> filteredModifiers = [];
foreach (var modifier in IdToTypeModifiers.Where(pair => pair.Value.IsAssignableTo(typeof(GameModifier))))
{
var mod = (GameModifier)Activator.CreateInstance(modifier.Value);
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);
if (randomNum < Math.Clamp(chance, 0, 100))
{
filteredModifiers.Add(TypeToIdModifiers[modifier.Value]);
}
}
}
var shuffledModifiers = filteredModifiers.Randomize();
if (shuffledModifiers.Count > plrs.Count)
{
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 (!IsGameModifierValid(plr, mod, id))
{
continue;
}
shuffledModifiers.RemoveAt(0);
plr.RpcAddModifier(id);
}
}
private static bool IsGameModifierValid(PlayerControl player, GameModifier modifier, uint modifierId)
{
return (player.Data.Role is not ICustomRole role || role.IsModifierApplicable(modifier)) &&
modifier.IsModifierValidOn(player.Data.Role) &&
!player.HasModifier(modifierId);
}
internal static void SyncAllModifiers(int targetId = -1)
{
var data = new List<NetData>();
foreach (var player in GameData.Instance.AllPlayers)
{
data.Add(GetPlayerModifiers(player.Object));
}
Rpc<SyncModifiersRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
}
internal static void HandleSyncModifiers(NetData[] data)
{
foreach (var netData in data)
{
var ids = new uint[netData.Data.Length / 4];
Buffer.BlockCopy(netData.Data, 0, ids, 0, netData.Data.Length);
var plr = GameData.Instance.GetPlayerById((byte)netData.Id).Object;
var modifierComponent = plr.GetComponent<ModifierComponent>();
if (!modifierComponent)
{
continue;
}
foreach (var modifier in modifierComponent.ActiveModifiers)
{
modifier.OnDeactivate();
}
modifierComponent.ActiveModifiers.Clear();
foreach (var id in ids)
{
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);
}
}
}
private static NetData GetPlayerModifiers(PlayerControl player)
{
var bytes = new List<byte>();
var modifierComponent = player.GetComponent<ModifierComponent>();
if (!modifierComponent)
{
return new NetData(player.PlayerId, []);
}
foreach (var modifier in modifierComponent.ActiveModifiers)
{
bytes.AddRange(BitConverter.GetBytes(TypeToIdModifiers[modifier.GetType()]));
}
return new NetData(player.PlayerId, bytes.ToArray());
}
}
@@ -0,0 +1,6 @@
using System;
namespace MiraAPI.Modifiers;
[AttributeUsage(AttributeTargets.Class)]
public class RegisterModifierAttribute : Attribute;
+7
View File
@@ -0,0 +1,7 @@
namespace MiraAPI.Modifiers.Types;
public abstract class GameModifier : BaseModifier
{
public abstract int GetAssignmentChance();
public abstract int GetAmountPerGame();
public virtual bool IsModifierValidOn(RoleBehaviour role) => true;
}
+52
View File
@@ -0,0 +1,52 @@
using MiraAPI.Utilities;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Modifiers.Types;
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; }
public float TimeRemaining { get; protected set; }
public override void FixedUpdate()
{
if (!Player.AmOwner)
{
return;
}
if (TimeRemaining > 0 && TimerActive)
{
TimeRemaining -= Time.fixedDeltaTime;
}
else
{
TimerActive = false;
TimeRemaining = Duration;
OnTimerComplete();
if (RemoveOnComplete)
{
Player.RpcRemoveModifier(ModifierId);
}
}
}
public void StartTimer()
{
if (TimerActive)
{
Logger<MiraApiPlugin>.Error("Can't start a timer that has already been started.");
return;
}
TimerActive = true;
TimeRemaining = Duration;
}
}
+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;
}
}
+5 -1
View File
@@ -3,5 +3,9 @@
public enum MiraRpc : uint
{
SyncGameOptions,
SyncRoleOptions
SyncRoleOptions,
AddModifier,
RemoveModifier,
SyncModifiers,
CustomMurder,
}
+2 -4
View File
@@ -1,10 +1,8 @@
using System;
namespace MiraAPI.Networking;
namespace MiraAPI.Networking;
public readonly struct NetData(uint id, byte[] data)
{
public uint Id { get; } = id;
public byte[] Data { get; } = data;
public int GetLength() => BitConverter.GetBytes(Id).Length + Data.Length;
public int GetLength() => 4 + Data.Length;
}
+46
View File
@@ -0,0 +1,46 @@
using Hazel;
using MiraAPI.Modifiers;
using Reactor.Networking.Attributes;
using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncModifiers)]
internal class SyncModifiersRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
public override void Write(MessageWriter writer, NetData[] data)
{
writer.WritePacked((uint)data.Length);
foreach (var netData in data)
{
writer.WritePacked(netData.Id);
writer.WriteBytesAndSize(netData.Data);
}
}
public override NetData[] Read(MessageReader reader)
{
var length = reader.ReadPackedUInt32();
var data = new NetData[length];
for (var i = 0; i < length; i++)
{
var id = reader.ReadPackedUInt32();
var bytes = reader.ReadBytesAndSize();
data[i] = new NetData(id, bytes);
}
return data;
}
public override void Handle(PlayerControl playerControl, NetData[] data)
{
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
{
return;
}
ModifierManager.HandleSyncModifiers(data);
}
}
+1 -1
View File
@@ -7,7 +7,7 @@ namespace MiraAPI.Networking;
// METHOD RPC DOESNT WORK WITH THE ARRAYS AND STUFF SO THIS IS HOW WE WILL DO IT FOR NOW
[RegisterCustomRpc((uint)MiraRpc.SyncGameOptions)]
public class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
internal class SyncOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
+1 -1
View File
@@ -6,7 +6,7 @@ using Reactor.Networking.Rpc;
namespace MiraAPI.Networking;
[RegisterCustomRpc((uint)MiraRpc.SyncRoleOptions)]
public class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
internal class SyncRoleOptionsRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<MiraApiPlugin, NetData[]>(plugin, id)
{
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
+3 -1
View File
@@ -1,6 +1,7 @@
using HarmonyLib;
using InnerNet;
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
namespace MiraAPI.Patches;
@@ -14,7 +15,7 @@ public static class AmongUsClientSyncPatch
{
return;
}
if (clientData.Id == AmongUsClient.Instance.HostId)
{
return;
@@ -22,5 +23,6 @@ public static class AmongUsClientSyncPatch
ModdedOptionsManager.SyncAllOptions(clientData.Id);
CustomRoleManager.SyncAllRoleSettings(clientData.Id);
ModifierManager.SyncAllModifiers(clientData.Id);
}
}
@@ -0,0 +1,58 @@
using HarmonyLib;
using MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Patches.Colors;
[HarmonyPatch(typeof(PlayerTab))]
public static class ScrollingColorsPatch
{
/// Collider
private static BoxCollider2D _collider;
/// <summary>
/// Add scrolling to the colors tab
/// </summary>
[HarmonyPostfix, HarmonyPatch("OnEnable")]
public static void AddScrollingToColorsTabPatch(PlayerTab __instance)
{
if (!PlayerCustomizationMenu.Instance)
{
return;
}
var tab = PlayerCustomizationMenu.Instance.Tabs[1].Tab;
if (__instance.scroller == null)
{
var newScroller = Object.Instantiate(tab.scroller, __instance.transform, true);
newScroller.Inner.transform.DestroyChildren();
var maskObj = new GameObject
{
layer = 5,
name = "SpriteMask"
};
maskObj.transform.SetParent(__instance.transform);
maskObj.transform.localPosition = new Vector3(0, 0, 0);
maskObj.transform.localScale = new Vector3(500, 4.76f);
var mask = maskObj.AddComponent<SpriteMask>();
mask.sprite = MiraAssets.Empty.LoadAsset();
_collider = maskObj.AddComponent<BoxCollider2D>();
_collider.size = new Vector2(1f, 0.75f);
_collider.enabled = true;
__instance.scroller = newScroller;
}
foreach (var chip in __instance.ColorChips)
{
chip.transform.SetParent(__instance.scroller.Inner);
chip.Button.ClickMask = _collider;
chip.GetComponent<SpriteRenderer>().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
__instance.SetScrollerBounds();
}
}
+22 -4
View File
@@ -1,9 +1,11 @@
using HarmonyLib;
using System;
using HarmonyLib;
using InnerNet;
using MiraAPI.Hud;
using MiraAPI.Roles;
using Reactor.Utilities.Extensions;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches;
@@ -78,10 +80,12 @@ public static class HudManagerPatches
[HarmonyPatch(nameof(HudManager.Start))]
public static void StartPostfix(HudManager __instance)
{
var buttons = __instance.transform.Find("Buttons");
var bottomRight = buttons.Find("BottomRight");
if (!_bottomLeft)
{
var buttons = __instance.transform.Find("Buttons");
_bottomLeft = Object.Instantiate(buttons.Find("BottomRight").gameObject, buttons);
_bottomLeft = Object.Instantiate(bottomRight.gameObject, buttons);
}
foreach (var t in _bottomLeft.GetComponentsInChildren<ActionButton>(true))
@@ -98,7 +102,19 @@ public static class HudManagerPatches
foreach (var button in CustomButtonManager.CustomButtons)
{
button.CreateButton(_bottomLeft.transform);
var location = button.Location switch
{
ButtonLocation.BottomLeft => _bottomLeft.transform,
ButtonLocation.BottomRight => bottomRight,
_ => null
};
if (location is null)
{
continue;
}
button.CreateButton(location);
}
gridArrange.Start();
@@ -125,7 +141,9 @@ public static class HudManagerPatches
}
foreach (var button in CustomButtonManager.CustomButtons)
{
button.SetActive(isActive, roleBehaviour);
}
if (roleBehaviour is ICustomRole role)
{
+1 -1
View File
@@ -12,7 +12,7 @@ public static class MeetingHudPatches
{
foreach (var customActionButton in CustomButtonManager.CustomButtons)
{
customActionButton.ResetCooldown();
customActionButton.ResetCooldownAndOrEffect();
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using HarmonyLib;
using MiraAPI.Utilities;
using System.Linq;
using UnityEngine;
namespace MiraAPI.Patches.Modifiers;
[HarmonyPatch(typeof(Vent))]
public static class VentPatches
{
[HarmonyPostfix]
[HarmonyPatch(nameof(Vent.CanUse))]
public static void CanUseVentPatch(Vent __instance, ref float __result, [HarmonyArgument(0)] NetworkedPlayerInfo pc, [HarmonyArgument(1)] ref bool canUse, [HarmonyArgument(2)] ref bool couldUse)
{
var @object = pc.Object;
var role = @object.Data.Role;
var modifiers = @object.GetModifierComponent()?.ActiveModifiers;
if (modifiers is null || modifiers.Count <= 0)
{
return;
}
switch (role.CanVent)
{
case true when modifiers.Any(x => !x.CanVent()):
couldUse = canUse = false;
return;
case false when modifiers.Any(x => x.CanVent()):
couldUse = true;
break;
}
var num = float.MaxValue;
canUse = couldUse;
if (canUse)
{
var center = @object.Collider.bounds.center;
var position = __instance.transform.position;
num = Vector2.Distance(center, position);
canUse &= num <= __instance.UsableDistance && !PhysicsHelpers.AnythingBetween(@object.Collider, center, position, Constants.ShipOnlyMask, false);
}
__result = num;
}
}
+125 -47
View File
@@ -1,11 +1,12 @@
using HarmonyLib;
using Il2CppSystem.Collections.Generic;
using MiraAPI.GameOptions;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using Reactor.Localization.Utilities;
using Reactor.Utilities.Extensions;
using System.Linq;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
namespace MiraAPI.Patches.Options;
@@ -13,6 +14,59 @@ namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(GameOptionsMenu))]
public static class GameOptionsMenuPatch
{
[HarmonyPostfix]
[HarmonyPatch(nameof(GameOptionsMenu.Update))]
public static void UpdatePatch(GameOptionsMenu __instance)
{
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
{
return;
}
var num = 2.1f;
var filteredGroups = GameSettingMenuPatches.SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null);
foreach (var group in filteredGroups)
{
var filteredOpts = group.Options.Where(x => x.Visible.Invoke()).ToList();
if (filteredOpts.Count == 0)
{
continue;
}
group.Header.gameObject.SetActive(true);
group.Header.transform.localScale = Vector3.one * 0.63f;
group.Header.transform.localPosition = new Vector3(-0.903f, num, -2f);
num -= 0.58f;
foreach (var opt in group.Options)
{
var newOpt = opt.OptionBehaviour;
if (opt.Visible.Invoke() == false)
{
newOpt.gameObject.SetActive(false);
continue;
}
if (!group.AllOptionsHidden)
{
newOpt.gameObject.SetActive(true);
newOpt.transform.localPosition = new Vector3(0.952f, num, -2f);
num -= 0.45f;
}
else
{
newOpt.gameObject.SetActive(false);
}
}
}
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
}
[HarmonyPrefix]
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings))]
public static bool SettingsPatch(GameOptionsMenu __instance)
@@ -24,52 +78,51 @@ public static class GameOptionsMenuPatch
__instance.MapPicker.gameObject.SetActive(false);
float num = 2.1f;
var filteredGroups = GameSettingMenuPatches.SelectedMod.OptionGroups.Where(x => x.AdvancedRole is null);
var filteredGroups = GameSettingMenuPatches.SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null);
foreach (AbstractOptionGroup group in filteredGroups)
foreach (var group in filteredGroups)
{
var filteredOpts = group.Options.Where(x => x.Visible.Invoke()).ToList();
if (filteredOpts.Count == 0)
{
continue;
}
CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
var categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
categoryHeaderMasked.SetHeader(CustomStringName.CreateAndRegister(group.GroupName), 20);
if (group.GroupColor != Color.clear)
{
categoryHeaderMasked.Background.color = group.GroupColor;
categoryHeaderMasked.Divider.color = group.GroupColor;
categoryHeaderMasked.Title.color = group.GroupColor.GetAlternateColor();
}
categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f);
num -= 0.63f;
categoryHeaderMasked.Background.size = new Vector2(categoryHeaderMasked.Background.size.x + 1.5f, categoryHeaderMasked.Background.size.y);
categoryHeaderMasked.gameObject.SetActive(false);
group.Header = categoryHeaderMasked;
foreach (var opt in filteredOpts)
var newText = Object.Instantiate(categoryHeaderMasked.Title, categoryHeaderMasked.transform);
newText.text = $"<size=70%>(Click to open)</size>";
newText.transform.localPosition = new Vector3(2.6249f, -0.165f, 0f);
newText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
var options = group.Options.Select(opt => opt.CreateOption(__instance.checkboxOrigin,
__instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.settingsContainer));
foreach (var newOpt in options)
{
OptionBehaviour newOpt = opt.CreateOption(__instance.checkboxOrigin, __instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.settingsContainer);
newOpt.transform.localPosition = new Vector3(0.952f, num, -2f);
newOpt.SetClickMask(__instance.ButtonClickMask);
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
for (int i = 0; i < componentsInChildren.Length; i++)
foreach (var renderer in componentsInChildren)
{
if (group.GroupColor != Color.clear)
{
componentsInChildren[i].color = group.GroupColor.GetAlternateColor();
if (componentsInChildren[i].transform.parent.TryGetComponent<GameOptionButton>(out var btn))
renderer.color = group.GroupColor.GetAlternateColor();
if (renderer.transform.parent.TryGetComponent<GameOptionButton>(out var btn))
{
btn.interactableColor = group.GroupColor.GetAlternateColor();
btn.interactableHoveredColor = Color.white;
}
}
componentsInChildren[i].material.SetInt(PlayerMaterial.MaskLayer, 20);
renderer.material.SetInt(PlayerMaterial.MaskLayer, 20);
}
foreach (TextMeshPro textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
foreach (var textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
{
if (group.GroupColor != Color.clear)
{
@@ -80,14 +133,37 @@ public static class GameOptionsMenuPatch
textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20);
}
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 != Color.clear ? group.GroupColor : MiraAssets.AcceptedTeal;
}
__instance.Children.Add(newOpt);
num -= 0.45f;
newOpt.Initialize();
newOpt.gameObject.SetActive(false);
}
var boxCol = categoryHeaderMasked.gameObject.AddComponent<BoxCollider2D>();
boxCol.size = new Vector2(7, 0.7f);
boxCol.offset = new Vector2(1.5f, -0.3f);
var headerBtn = categoryHeaderMasked.gameObject.AddComponent<PassiveButton>();
headerBtn.ClickSound = __instance.BackButton.GetComponent<PassiveButton>().ClickSound;
headerBtn.OnMouseOver = new UnityEvent();
headerBtn.OnMouseOut = new UnityEvent();
headerBtn.OnClick.AddListener((UnityAction)(() =>
{
group.AllOptionsHidden = !group.AllOptionsHidden;
newText.text = group.AllOptionsHidden ? "<size=70%>(Click to open)</size>" : "<size=70%>(Click to close)</size>";
}));
headerBtn.SetButtonEnableState(true);
}
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
return false;
}
@@ -95,30 +171,32 @@ public static class GameOptionsMenuPatch
[HarmonyPatch(nameof(GameOptionsMenu.Initialize))]
public static bool InitPatch(GameOptionsMenu __instance)
{
if (__instance.Children == null || __instance.Children.Count == 0)
if (__instance.Children != null && __instance.Children.Count != 0)
{
__instance.MapPicker.gameObject.SetActive(true);
__instance.MapPicker.Initialize(20);
BaseGameSetting mapNameSetting = GameManager.Instance.GameSettingsList.MapNameSetting;
__instance.MapPicker.SetUpFromData(mapNameSetting, 20);
__instance.Children = new List<OptionBehaviour>();
__instance.Children.Add(__instance.MapPicker);
__instance.CreateSettings();
__instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions;
foreach (var optionBehaviour in __instance.Children)
return false;
}
__instance.MapPicker.gameObject.SetActive(true);
__instance.MapPicker.Initialize(20);
var mapNameSetting = GameManager.Instance.GameSettingsList.MapNameSetting;
__instance.MapPicker.SetUpFromData(mapNameSetting, 20);
__instance.Children = new Il2CppSystem.Collections.Generic.List<OptionBehaviour>();
__instance.Children.Add(__instance.MapPicker);
__instance.CreateSettings();
__instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions;
foreach (var optionBehaviour in __instance.Children)
{
if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost)
{
if (AmongUsClient.Instance && !AmongUsClient.Instance.AmHost)
{
optionBehaviour.SetAsPlayer();
}
if (optionBehaviour.IsCustom())
{
continue;
}
optionBehaviour.OnValueChanged = new System.Action<OptionBehaviour>(__instance.ValueChanged);
optionBehaviour.SetAsPlayer();
}
if (optionBehaviour.IsCustom())
{
continue;
}
optionBehaviour.OnValueChanged = new System.Action<OptionBehaviour>(__instance.ValueChanged);
}
return false;
@@ -1,4 +1,5 @@
using HarmonyLib;
#nullable enable
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities.Assets;
using Reactor.Utilities.Extensions;
@@ -17,18 +18,21 @@ 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;
[HarmonyPrefix]
[HarmonyPatch(nameof(GameSettingMenu.Start))]
public static void StartPrefix(GameSettingMenu __instance)
{
_roleBtnOgPos = __instance.RoleSettingsButton.transform.localPosition;
__instance.transform.FindChild("GameSettingsLabel").gameObject.SetActive(false);
Transform helpThing = __instance.transform.FindChild("What Is This?");
GameObject tmpText = Object.Instantiate(helpThing.transform.FindChild("InfoText"), helpThing.parent).gameObject;
var helpThing = __instance.transform.FindChild("What Is This?");
var tmpText = Object.Instantiate(helpThing.transform.FindChild("InfoText"), helpThing.parent).gameObject;
tmpText.GetComponent<TextTranslatorTMP>().Destroy();
tmpText.name = "SelectedMod";
@@ -36,29 +40,31 @@ public static class GameSettingMenuPatches
_text = tmpText.GetComponent<TextMeshPro>();
_text.fontSizeMax = 3.2f;
UpdateText(__instance.GameSettingsTab, __instance.RoleSettingsTab);
_text.overflowMode = TextOverflowModes.Overflow;
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
_text.alignment = TextAlignmentOptions.Center;
GameObject nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
nextButton.name = "RightArrowButton";
nextButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite = MiraAssets.NextButton.LoadAsset();
nextButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite = MiraAssets.NextButtonActive.LoadAsset();
nextButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
PassiveButton passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
passiveButton.OnClick = new ButtonClickedEvent();
passiveButton.OnClick.AddListener((UnityAction)(() =>
{
if (CurrentSelectedMod != MiraPluginManager.Instance.RegisteredPlugins.Count)
{
CurrentSelectedMod += 1;
UpdateText(__instance.GameSettingsTab, __instance.RoleSettingsTab);
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
}
}));
GameObject backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
backButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
backButton.name = "LeftArrowButton";
backButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
@@ -68,33 +74,55 @@ public static class GameSettingMenuPatches
if (CurrentSelectedMod != 0)
{
CurrentSelectedMod -= 1;
UpdateText(__instance.GameSettingsTab, __instance.RoleSettingsTab);
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
}
}));
}
private static void UpdateText(GameOptionsMenu settings, RolesSettingsMenu roles)
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 = 2f;
_text.fontSizeMax = 2.3f;
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod - 1).Value;
if (SelectedMod == null)
{
CurrentSelectedMod = 0;
UpdateText(settings, roles);
UpdateText(menu, settings, roles);
}
string name = SelectedMod?.PluginInfo.Metadata.Name;
_text.text = name?[..Math.Min(name.Length, 15)];
var name = SelectedMod?.MiraPlugin.OptionsTitleText;
_text.text = name?[..Math.Min(name.Length, 25)];
}
if (roles?.roleChances != null)
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
menu.GameSettingsButton.gameObject.SetActive(true);
menu.RoleSettingsButton.gameObject.SetActive(true);
if (CurrentSelectedMod != 0)
{
if (SelectedMod?.Options.Where(x=>x.AdvancedRole==null).ToList().Count == 0)
{
menu.GameSettingsButton.gameObject.SetActive(false);
}
if (SelectedMod?.CustomRoles.Count == 0)
{
menu.RoleSettingsButton.gameObject.SetActive(false);
}
if (!menu.GameSettingsButton.gameObject.active && menu.RoleSettingsButton.gameObject.active)
{
menu.RoleSettingsButton.transform.localPosition = menu.GameSettingsButton.transform.localPosition;
}
}
if (roles?.roleChances != null && SelectedMod != null && SelectedMod.CustomRoles.Count != 0)
{
if (roles.advancedSettingChildren is not null)
{
@@ -126,7 +154,7 @@ public static class GameSettingMenuPatches
roles.scrollBar.ScrollToTop();
}
if (settings?.Children != null)
if (settings.Children != null && SelectedMod?.OptionGroups.Count != 0)
{
foreach (var child in settings.Children)
{
+338 -5
View File
@@ -1,14 +1,28 @@
using HarmonyLib;
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.UI;
using Object = UnityEngine.Object;
namespace MiraAPI.Patches.Options;
[HarmonyPatch(typeof(LobbyViewSettingsPane))]
public static class LobbyViewPanePatches
{
public static int CurrentSelectedMod { get; private set; }
public static MiraPluginInfo? SelectedMod => CurrentSelectedMod == 0 ? null : MiraPluginManager.Instance.RegisteredPlugins.ElementAt(CurrentSelectedMod-1).Value;
[HarmonyPostfix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))]
public static void AwakePatch(LobbyViewSettingsPane __instance)
@@ -16,7 +30,7 @@ public static class LobbyViewPanePatches
__instance.gameModeText.transform.localPosition = new Vector3(-2.3f, 2.4f, -2);
// Create the next button
GameObject nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
nextButton.GetComponent<BoxCollider2D>().size = new Vector2(0.2f, 0.3f);
nextButton.transform.localPosition = new Vector3(-5.4f, 2.4f, -2f);
nextButton.transform.localScale = new Vector3(3, 3, 2);
@@ -30,24 +44,343 @@ public static class LobbyViewPanePatches
hover.transform.localPosition = new Vector3(0, 0f, 0.3f);
hover.sprite = MiraAssets.NextButtonActive.LoadAsset();
PassiveButton passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
passiveButton.OnClick = new Button.ButtonClickedEvent();
passiveButton.OnClick.AddListener((UnityAction)(() =>
{
CurrentSelectedMod += 1;
if (CurrentSelectedMod > MiraPluginManager.Instance.RegisteredPlugins.Count)
{
CurrentSelectedMod = 0;
}
__instance.RefreshTab();
__instance.scrollBar.ScrollToTop();
}));
// Create the back button
GameObject backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
backButton.transform.localPosition = new Vector3(-6.3f, 2.4f, -2f);
backButton.name = "LeftArrowButton";
backButton.transform.FindChild("Normal").gameObject.GetComponentInChildren<SpriteRenderer>().flipX
= backButton.transform.FindChild("Hover").gameObject.GetComponentInChildren<SpriteRenderer>().flipX
= true;
PassiveButton passiveButton2 = backButton.gameObject.GetComponent<PassiveButton>();
var passiveButton2 = backButton.gameObject.GetComponent<PassiveButton>();
passiveButton2.OnClick = new Button.ButtonClickedEvent();
passiveButton2.OnClick.AddListener((UnityAction)(() =>
{
CurrentSelectedMod -= 1;
if (CurrentSelectedMod < 0)
{
CurrentSelectedMod = MiraPluginManager.Instance.RegisteredPlugins.Count;
}
__instance.RefreshTab();
__instance.scrollBar.ScrollToTop();
}));
}
[HarmonyPostfix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.Update))]
public static void UpdatePatch(LobbyViewSettingsPane __instance)
{
__instance.gameModeText.text = SelectedMod?.PluginInfo.Metadata.Name ?? "Default";
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.DrawNormalTab))]
public static bool DrawNormalTabPatch(LobbyViewSettingsPane __instance)
{
if (CurrentSelectedMod == 0)
{
return true;
}
DrawOptionsTab(__instance);
return false;
}
[HarmonyPrefix]
[HarmonyPatch(nameof(LobbyViewSettingsPane.DrawRolesTab))]
public static bool DrawRolesTabPatch(LobbyViewSettingsPane __instance)
{
if (CurrentSelectedMod == 0)
{
return true;
}
DrawRolesTab(__instance);
return false;
}
private static void DrawOptionsTab(LobbyViewSettingsPane instance)
{
if (SelectedMod == null)
{
return;
}
var num = 1.44f;
var filteredGroups = SelectedMod.OptionGroups.Where(x => x.GroupVisible.Invoke() && x.AdvancedRole is null);
foreach (var group in filteredGroups)
{
if (group.Options.Count == 0)
{
continue;
}
var categoryHeaderMasked = Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.Name, 61);
categoryHeaderMasked.Title.text = group.GroupName;
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
num -= 0.85f;
var i = 0;
foreach (var option in group.Options)
{
if (!option.Visible.Invoke())
{
continue;
}
var viewSettingsInfoPanel = Object.Instantiate(instance.infoPanelOrigin, instance.settingsContainer, true);
viewSettingsInfoPanel.transform.localScale = Vector3.one;
float num2;
if (i % 2 == 0)
{
num2 = -8.95f;
if (i > 0)
{
num -= 0.59f;
}
}
else
{
num2 = -3f;
}
viewSettingsInfoPanel.transform.localPosition = new Vector3(num2, num, -2f);
var data = option.Data;
if (data.Type == OptionTypes.Checkbox)
{
viewSettingsInfoPanel.SetInfoCheckbox(data.Title, 61, Mathf.Approximately(option.GetFloatData(), 1));
}
else
{
viewSettingsInfoPanel.SetInfo(data.Title, data.GetValueString(option.GetFloatData()), 61);
}
instance.settingsInfo.Add(viewSettingsInfoPanel.gameObject);
i++;
}
num -= 0.59f;
}
instance.scrollBar.CalculateAndSetYBounds(instance.settingsInfo.Count + 10, 2f, 6f, 0.59f);
}
private static void DrawRolesTab(LobbyViewSettingsPane instance)
{
if (SelectedMod == null)
{
return;
}
var num = 0.95f;
var num2 = -6.53f;
var categoryHeaderMasked =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
var list = new List<Type>();
foreach (var team in Enum.GetValues<ModdedRoleTeams>())
{
var filteredRoles = SelectedMod.CustomRoles.Values
.OfType<ICustomRole>()
.Where(x => !x.HideSettings && x.Team == team).ToList();
if (filteredRoles.Count == 0)
{
continue;
}
var categoryHeaderRoleVariant =
Object.Instantiate(instance.categoryHeaderRoleOrigin, instance.settingsContainer, true);
switch (team)
{
case ModdedRoleTeams.Crewmate:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
break;
case ModdedRoleTeams.Impostor:
categoryHeaderRoleVariant.SetHeader(StringNames.ImpostorRolesHeader, 61);
break;
case ModdedRoleTeams.Neutral:
default:
categoryHeaderRoleVariant.SetHeader(StringNames.CrewmateRolesHeader, 61);
categoryHeaderRoleVariant.Title.text = team + " Roles";
categoryHeaderRoleVariant.Background.color = Color.gray;
categoryHeaderRoleVariant.Title.color = Color.white;
break;
}
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
num -= 0.696f;
foreach (var customRole in filteredRoles)
{
var roleBehaviour = customRole as RoleBehaviour;
if (roleBehaviour == null)
{
continue;
}
var chancePerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(roleBehaviour.Role);
var numPerGame =
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetNumPerGame(roleBehaviour.Role);
var flag = numPerGame == 0;
var viewSettingsInfoPanelRoleVariant =
Object.Instantiate(
instance.infoPanelRoleOrigin, instance.settingsContainer, true);
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
var advancedRoleOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == customRole.GetType())
.ToList();
if (!flag && advancedRoleOptions.Count > 0)
{
list.Add(customRole.GetType());
}
viewSettingsInfoPanelRoleVariant.SetInfo(roleBehaviour.NiceName, numPerGame, chancePerGame,
61, customRole.RoleColor, customRole.Icon.LoadAsset(), true);
viewSettingsInfoPanelRoleVariant.titleText.color =
viewSettingsInfoPanelRoleVariant.chanceTitle.color =
viewSettingsInfoPanelRoleVariant.chanceBackground.color =
viewSettingsInfoPanelRoleVariant.background.color =
customRole.RoleColor.GetAlternateColor();
instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
num -= 0.664f;
}
}
if (list.Count > 0)
{
var categoryHeaderMasked2 =
Object.Instantiate(instance.categoryHeaderOrigin, instance.settingsContainer, true);
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
categoryHeaderMasked2.transform.localScale = Vector3.one;
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
num -= 1.7f;
var num3 = 0f;
for (var k = 0; k < list.Count; k++)
{
float num4;
if (k % 2 == 0)
{
num4 = -5.8f;
if (k > 0)
{
num -= num3 + 0.59f;
num3 = 0f;
}
}
else
{
num4 = 0.14999962f;
}
var advancedRoleViewPanel =
Object.Instantiate(instance.advancedRolePanelOrigin, instance.settingsContainer, true);
advancedRoleViewPanel.transform.localScale = Vector3.one;
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
var num5 = SetUpAdvancedRoleViewPanel(advancedRoleViewPanel, list[k], 0.59f, 61);
if (num5 > num3)
{
num3 = num5;
}
instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
}
}
instance.scrollBar.SetYBoundsMax(-num);
}
private static float SetUpAdvancedRoleViewPanel(AdvancedRoleViewPanel viewPanel, Type roleType, float spacingY, int maskLayer)
{
if (SelectedMod == null)
{
return 0;
}
var role = SelectedMod.CustomRoles.Values.FirstOrDefault(x => x.GetType() == roleType);
if (role == null)
{
return 0;
}
if (role is not ICustomRole customRole)
{
return 0;
}
viewPanel.header.SetHeader(role.StringName, maskLayer, role.TeamType == RoleTeamTypes.Crewmate, customRole.Icon.LoadAsset());
viewPanel.divider.material.SetInt(PlayerMaterial.MaskLayer, maskLayer);
var num = viewPanel.yPosStart;
var num2 = 1.08f;
var filteredOptions = SelectedMod.Options
.Where(x => x.AdvancedRole == roleType)
.ToList();
for (var i = 0; i < filteredOptions.Count; i++)
{
var option = filteredOptions[i];
var baseGameSetting = option.Data;
var viewSettingsInfoPanel = Object.Instantiate(viewPanel.infoPanelOrigin, viewPanel.transform, true);
viewSettingsInfoPanel.transform.localScale = Vector3.one;
viewSettingsInfoPanel.transform.localPosition = new Vector3(viewPanel.xPosStart, num, -2f);
var value = option.GetFloatData();
if (baseGameSetting.Type == OptionTypes.Checkbox)
{
viewSettingsInfoPanel.SetInfoCheckbox(baseGameSetting.Title, maskLayer, value > 0f);
}
else
{
viewSettingsInfoPanel.SetInfo(baseGameSetting.Title, baseGameSetting.GetValueString(value), maskLayer);
}
num -= spacingY;
if (i > 0)
{
num2 += 0.8f;
}
}
return num2;
}
}
@@ -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;
@@ -41,7 +42,7 @@ public static class RoleSettingMenuPatches
return true;
}
float num = 0.522f;
var num = 0.522f;
__instance.AllButton.transform.parent.gameObject.SetActive(false);
__instance.AllButton.gameObject.SetActive(false);
@@ -51,7 +52,7 @@ public static class RoleSettingMenuPatches
hitbox.localPosition = new Vector3(0.3297f, -.6333f, 4f);
hitbox.localScale = new Vector3(1, 1.2f, 1);
int num3 = 0;
var num3 = 0;
var crewRoles = GameSettingMenuPatches.SelectedMod.CustomRoles.Values
.OfType<ICustomRole>()
@@ -61,7 +62,7 @@ public static class RoleSettingMenuPatches
if (crewRoles.Count > 0)
{
CategoryHeaderEditRole categoryHeaderEditRole = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
var categoryHeaderEditRole = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20);
categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f);
num -= 0.522f;
@@ -81,7 +82,7 @@ public static class RoleSettingMenuPatches
if (impRoles.Count > 0)
{
num -= 0.4f;
CategoryHeaderEditRole categoryHeaderEditRole2 = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin,
var categoryHeaderEditRole2 = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin,
Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20);
categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f);
@@ -103,10 +104,12 @@ public static class RoleSettingMenuPatches
if (neutRoles.Count > 0)
{
num -= 0.4f;
CategoryHeaderEditRole categoryHeaderEditRole3 = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin,
var categoryHeaderEditRole3 = Object.Instantiate(__instance.categoryHeaderEditRoleOrigin,
Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
categoryHeaderEditRole3.SetHeader(StringNames.None, 20);
categoryHeaderEditRole3.Title.text = "Neutral Roles";
categoryHeaderEditRole3.Background.color = Color.gray;
categoryHeaderEditRole3.Title.color = Color.white;
categoryHeaderEditRole3.transform.localPosition = new Vector3(4.986f, num, -2f);
num -= 0.522f;
@@ -124,12 +127,7 @@ public static class RoleSettingMenuPatches
[HarmonyPatch(nameof(RolesSettingsMenu.Update))]
public static bool UpdatePatch()
{
if (GameSettingMenuPatches.CurrentSelectedMod == 0)
{
return true;
}
return false;
return GameSettingMenuPatches.CurrentSelectedMod == 0;
}
[HarmonyPostfix]
@@ -167,7 +165,7 @@ public static class RoleSettingMenuPatches
if (AmongUsClient.Instance.AmHost)
{
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData(roleSetting.Role)], true);
Rpc<SyncRoleOptionsRpc>.Instance.Send(PlayerControl.LocalPlayer, [role.GetNetData()], true);
}
GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
}
@@ -181,7 +179,7 @@ public static class RoleSettingMenuPatches
__instance.advancedSettingChildren.Clear();
float num = -0.872f;
var num = -0.872f;
var filteredOptions = GameSettingMenuPatches.SelectedMod.Options.Where(x => x.AdvancedRole == role.GetType());
@@ -192,16 +190,16 @@ public static class RoleSettingMenuPatches
continue;
}
OptionBehaviour newOpt = option.CreateOption(__instance.checkboxOrigin, __instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.AdvancedRolesSettings.transform);
var newOpt = option.CreateOption(__instance.checkboxOrigin, __instance.numberOptionOrigin, __instance.stringOptionOrigin, __instance.AdvancedRolesSettings.transform);
newOpt.transform.localPosition = new Vector3(2.17f, num, -2f);
newOpt.SetClickMask(__instance.ButtonClickMask);
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
for (int i = 0; i < componentsInChildren.Length; i++)
foreach (var renderer in componentsInChildren)
{
componentsInChildren[i].material.SetInt(PlayerMaterial.MaskLayer, 20);
renderer.material.SetInt(PlayerMaterial.MaskLayer, 20);
}
foreach (TextMeshPro textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
foreach (var textMeshPro in newOpt.GetComponentsInChildren<TextMeshPro>(true))
{
textMeshPro.fontMaterial.SetFloat(ShaderID.StencilComp, 3f);
textMeshPro.fontMaterial.SetFloat(ShaderID.Stencil, 20);
@@ -219,18 +217,27 @@ public static class RoleSettingMenuPatches
}
private static void ChangeTab(RoleBehaviour role, RolesSettingsMenu __instance)
{
ICustomRole customRole = role as ICustomRole;
var customRole = role as ICustomRole;
__instance.roleDescriptionText.text = customRole.RoleLongDescription;
__instance.roleTitleText.text = DestroyableSingleton<TranslationController>.Instance.GetString(role.StringName, Array.Empty<Il2CppSystem.Object>());
__instance.roleTitleText.text = DestroyableSingleton<TranslationController>.Instance.GetString(role.StringName, new Il2CppReferenceArray<Il2CppSystem.Object>(0));
__instance.roleScreenshot.sprite = Sprite.Create(customRole.OptionsScreenshot.LoadAsset().texture, new Rect(0, 0, 370, 230), Vector2.one / 2, 100);
__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);
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)
{
@@ -244,8 +251,8 @@ public static class RoleSettingMenuPatches
public static void CreateQuotaOption(RolesSettingsMenu __instance, RoleBehaviour role, ref float yPos, int index)
{
ICustomRole customRole = role as ICustomRole;
RoleOptionSetting roleOptionSetting = Object.Instantiate(__instance.roleOptionSettingOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
var customRole = role as ICustomRole;
var roleOptionSetting = Object.Instantiate(__instance.roleOptionSettingOrigin, Vector3.zero, Quaternion.identity, __instance.RoleChancesSettings.transform);
roleOptionSetting.transform.localPosition = new Vector3(-0.15f, yPos, -2f);
roleOptionSetting.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, role, 20);
roleOptionSetting.labelSprite.color = customRole.RoleColor;
@@ -257,26 +264,27 @@ public static class RoleSettingMenuPatches
roleOptionSetting.titleText.color = customRole.RoleColor.GetAlternateColor();
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
PassiveButton 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();
SpriteRenderer btnRend = newButton.transform.FindChild("ButtonSprite").GetComponent<SpriteRenderer>();
btnRend.sprite = MiraAssets.Cog.LoadAsset();
GameOptionButton 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;
}
}
+24 -6
View File
@@ -1,29 +1,47 @@
using HarmonyLib;
using MiraAPI.Hud;
using MiraAPI.Roles;
using MiraAPI.Modifiers;
using Reactor.Utilities.Extensions;
namespace MiraAPI.Patches;
[HarmonyPatch(typeof(PlayerControl))]
public static class PlayerControlPatches
{
[HarmonyPrefix]
[HarmonyPatch(nameof(PlayerControl.Start))]
public static void PlayerControlStartPostfix(PlayerControl __instance)
{
if (__instance.gameObject.TryGetComponent<ModifierComponent>(out var comp))
{
comp.DestroyImmediate();
}
__instance.gameObject.AddComponent<ModifierComponent>();
}
[HarmonyPostfix]
[HarmonyPatch(nameof(PlayerControl.FixedUpdate))]
public static void PlayerControlFixedUpdatePostfix(PlayerControl __instance)
{
if (!__instance.AmOwner)
{
return;
}
if (__instance.Data?.Role is ICustomRole customRole)
{
customRole.PlayerControlFixedUpdate(__instance);
}
if (!__instance.AmOwner)
{
return;
}
foreach (var button in CustomButtonManager.CustomButtons)
{
if (__instance.Data?.Role == null)
{
continue;
}
if (!button.Enabled(__instance.Data?.Role))
{
continue;
@@ -1,4 +1,5 @@
using HarmonyLib;
using MiraAPI.Colors;
using MiraAPI.Roles;
namespace MiraAPI.Patches.Roles;
@@ -22,5 +23,6 @@ public static class GameStartupPatch
_runOnce = true;
CustomRoleManager.RegisterInRoleManager();
PaletteManager.RegisterAllColors();
}
}
+24 -8
View File
@@ -1,6 +1,8 @@
using AmongUs.GameOptions;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using System.Linq;
using UnityEngine;
namespace MiraAPI.Patches.Roles;
@@ -8,6 +10,18 @@ namespace MiraAPI.Patches.Roles;
[HarmonyPatch(typeof(RoleManager))]
public static class RoleManagerPatches
{
[HarmonyPostfix]
[HarmonyPatch(nameof(RoleManager.SelectRoles))]
public static void ModifierSelectionPatches(RoleManager __instance)
{
if (!AmongUsClient.Instance.AmHost)
{
return;
}
ModifierManager.AssignModifiers(PlayerControl.AllPlayerControls.ToArray().Where(plr => !plr.Data.IsDead && !plr.Data.Disconnected).ToList());
}
[HarmonyPrefix]
[HarmonyPatch(nameof(RoleManager.AssignRoleOnDeath))]
public static bool AssignRoleOnDeath(RoleManager __instance, [HarmonyArgument(0)] PlayerControl plr)
@@ -17,16 +31,18 @@ public static class RoleManagerPatches
return false;
}
if (plr.Data.Role is ICustomRole role)
if (plr.Data.Role is not ICustomRole role)
{
Debug.Log(role.RoleName);
if (role.GhostRole != RoleTypes.CrewmateGhost && role.GhostRole != RoleTypes.ImpostorGhost)
{
plr.RpcSetRole(role.GhostRole);
return false;
}
return true;
}
return true;
if (role.GhostRole is RoleTypes.CrewmateGhost or RoleTypes.ImpostorGhost)
{
return true;
}
plr.RpcSetRole(role.GhostRole);
return false;
}
}
@@ -14,19 +14,20 @@ public static class RoleOptionsCollectionPatch
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetChancePerGame))]
public static bool GetChancePrefix([HarmonyArgument(0)] RoleTypes roleType, ref int __result)
{
if (CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole))
if (!CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole))
{
return true;
}
if (customRole.HideSettings)
{
if (customRole.HideSettings)
{
return false;
}
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.ChanceConfigDefinition, out var entry);
__result = entry.Value;
return false;
}
return true;
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.ChanceConfigDefinition, out var entry);
__result = entry.Value;
return false;
}
/// <summary>
@@ -36,18 +37,18 @@ public static class RoleOptionsCollectionPatch
[HarmonyPatch(nameof(RoleOptionsCollectionV08.GetNumPerGame))]
public static bool GetNumPrefix([HarmonyArgument(0)] RoleTypes roleType, ref int __result)
{
if (CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole))
if (!CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole))
{
return true;
}
if (customRole.HideSettings)
{
if (customRole.HideSettings)
{
return false;
}
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.NumConfigDefinition, out var entry);
__result = entry.Value;
return false;
}
return true;
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.NumConfigDefinition, out var entry);
__result = entry.Value;
return false;
}
}
+88 -23
View File
@@ -5,7 +5,9 @@ using Il2CppSystem.Collections.Generic;
using Il2CppSystem.Text;
using MiraAPI.PluginLoading;
using MiraAPI.Roles;
using MiraAPI.Utilities.Assets;
using System.Linq;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -14,23 +16,59 @@ namespace MiraAPI.Patches.Roles;
[HarmonyPatch]
public static class TaskAdderPatch
{
public static TaskFolder RolesFolder;
public static System.Collections.Generic.Dictionary<string, string> ModsFolders = new System.Collections.Generic.Dictionary<string, string>();
private static TaskFolder _rolesFolder;
private static readonly System.Collections.Generic.Dictionary<string, string> ModsFolders = new();
private static Scroller _scroller;
[HarmonyPostfix]
[HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))]
public static void AddRolesFolder(TaskAdderGame __instance)
{
RolesFolder = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
RolesFolder.gameObject.SetActive(false);
RolesFolder.FolderName = "Roles";
RolesFolder.name = "RolesFolder";
GameObject inner = new("Inner");
inner.transform.SetParent(__instance.TaskParent.transform, false);
_scroller = __instance.TaskParent.gameObject.AddComponent<Scroller>();
_scroller.allowX = false;
_scroller.allowY = true;
_scroller.Inner = inner.transform;
GameObject hitbox = new("Hitbox")
{
layer = 5
};
hitbox.transform.SetParent(__instance.TaskParent.transform, false);
hitbox.transform.localScale = new Vector3(7.5f, 6.5f, 1);
hitbox.transform.localPosition = new Vector3(2.8f, -2.2f, 0);
SpriteMask mask = hitbox.AddComponent<SpriteMask>();
mask.sprite = MiraAssets.NextButton.LoadAsset();
mask.alphaCutoff = 0.0f;
BoxCollider2D collider = hitbox.AddComponent<BoxCollider2D>();
collider.size = new Vector2(1f, 1f);
collider.enabled = true;
_scroller.ClickMask = collider;
__instance.TaskPrefab.GetComponent<PassiveButton>().ClickMask = collider;
__instance.RoleButton.GetComponent<PassiveButton>().ClickMask = collider;
__instance.RootFolderPrefab.GetComponent<PassiveButton>().ClickMask = collider;
__instance.RootFolderPrefab.gameObject.SetActive(false);
__instance.TaskParent = inner.transform;
_rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
_rolesFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
_rolesFolder.gameObject.SetActive(false);
_rolesFolder.FolderName = "Roles";
_rolesFolder.name = "RolesFolder";
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins.Values)
{
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
var newFolder = Object.Instantiate(__instance.RootFolderPrefab, _scroller.Inner);
newFolder.FolderName = newFolder.name = plugin.PluginInfo.Metadata.Name;
newFolder.gameObject.SetActive(false);
RolesFolder.SubFolders.Add(newFolder);
_rolesFolder.SubFolders.Add(newFolder);
if (!ModsFolders.ContainsKey(plugin.PluginInfo.Metadata.Name))
{
@@ -38,7 +76,7 @@ public static class TaskAdderPatch
}
}
__instance.Root.SubFolders.Add(RolesFolder);
__instance.Root.SubFolders.Add(_rolesFolder);
__instance.GoToRoot();
}
@@ -47,16 +85,30 @@ public static class TaskAdderPatch
[HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)]
public static void RoleGetterPatch(TaskAddButton __instance)
{
if (__instance.role is ICustomRole lpRole)
if (__instance.role is ICustomRole { IsNeutral: true })
{
if (lpRole.IsNeutral)
{
__instance.FileImage.color = Color.gray;
}
__instance.FileImage.color = Color.gray;
}
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
}
private static void AddFileAsChildCustom(this TaskAdderGame instance, TaskFolder taskFolder, TaskAddButton item, ref float xCursor, ref float yCursor, ref float maxHeight)
{
item.transform.SetParent(instance.TaskParent);
item.transform.localPosition = new Vector3(xCursor, yCursor, 0f);
item.transform.localScale = Vector3.one;
maxHeight = Mathf.Max(maxHeight, item.Text.bounds.size.y + 1.3f);
xCursor += instance.fileWidth;
if (xCursor > instance.lineWidth)
{
xCursor = 0f;
yCursor -= maxHeight;
maxHeight = 0f;
}
instance.ActiveItems.Add(item.transform);
}
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
[HarmonyPrefix]
@@ -89,7 +141,7 @@ public static class TaskAdderPatch
folderTransform.localPosition = new Vector3(num, num2, 0f);
folderTransform.localScale = Vector3.one;
num3 = Mathf.Max(num3, taskFolder2.Text.bounds.size.y + 1.1f);
num3 = Mathf.Max(num3, taskFolder2.Text.bounds.size.y + 1.3f);
num += __instance.folderWidth;
if (num > __instance.lineWidth)
{
@@ -137,7 +189,7 @@ public static class TaskAdderPatch
{
taskAddButton.Text.text = DestroyableSingleton<TranslationController>.Instance.GetString(taskAddButton.MyTask.TaskType);
}
__instance.AddFileAsChild(taskFolder, taskAddButton, ref num, ref num2, ref num3);
__instance.AddFileAsChildCustom(taskFolder, taskAddButton, ref num, ref num2, ref num3);
if (taskAddButton != null && taskAddButton.Button != null)
{
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
@@ -155,15 +207,15 @@ public static class TaskAdderPatch
}
if (taskFolder.FolderName == "Roles")
{
for (int m = 0; m < DestroyableSingleton<RoleManager>.Instance.AllRoles.Length; m++)
for (var m = 0; m < DestroyableSingleton<RoleManager>.Instance.AllRoles.Length; m++)
{
RoleBehaviour roleBehaviour = DestroyableSingleton<RoleManager>.Instance.AllRoles[m];
var roleBehaviour = DestroyableSingleton<RoleManager>.Instance.AllRoles[m];
if (roleBehaviour.Role != RoleTypes.ImpostorGhost && roleBehaviour.Role != RoleTypes.CrewmateGhost && !CustomRoleManager.CustomRoles.ContainsKey((ushort)roleBehaviour.Role))
{
TaskAddButton taskAddButton2 = Object.Instantiate(__instance.RoleButton);
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
__instance.AddFileAsChild(RolesFolder, taskAddButton2, ref num, ref num2, ref num3);
__instance.AddFileAsChildCustom(_rolesFolder, taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
if (taskAddButton2 != null && taskAddButton2.Button != null)
{
@@ -183,9 +235,9 @@ public static class TaskAdderPatch
}
}
if (ModsFolders.TryGetValue(taskFolder.FolderName, out string guid))
if (ModsFolders.TryGetValue(taskFolder.FolderName, out var guid))
{
MiraPluginInfo plugin = MiraPluginManager.Instance.GetPluginByGuid(guid);
var plugin = MiraPluginManager.Instance.GetPluginByGuid(guid);
for (var m = 0; m < plugin.CustomRoles.Count; m++)
{
var roleBehaviour = plugin.CustomRoles.ElementAt(m).Value;
@@ -194,7 +246,7 @@ public static class TaskAdderPatch
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
__instance.AddFileAsChild(RolesFolder, taskAddButton2, ref num, ref num2, ref num3);
__instance.AddFileAsChildCustom(_rolesFolder, taskAddButton2, ref num, ref num2, ref num3);
taskAddButton2.Role = roleBehaviour;
if (taskAddButton2 != null && taskAddButton2.Button != null)
{
@@ -213,6 +265,19 @@ public static class TaskAdderPatch
}
}
}
foreach (var chip in __instance.ActiveItems)
{
chip.GetComponentInChildren<TextMeshPro>().EnableMasking();
chip.GetComponent<SpriteRenderer>().maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
}
if (_scroller)
{
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 3, 1f);
_scroller.SetYBoundsMin(0.0f);
_scroller.ScrollToTop();
}
if (__instance.Hierarchy.Count == 1)
{
ControllerManager.Instance.SetBackButton(__instance.BackButton);
+1
View File
@@ -4,5 +4,6 @@ namespace MiraAPI.PluginLoading;
public interface IMiraPlugin
{
string OptionsTitleText { get; }
public ConfigFile GetConfigFile();
}
+6 -6
View File
@@ -6,16 +6,16 @@ using System.Collections.Generic;
namespace MiraAPI.PluginLoading;
public class MiraPluginInfo(string id, IMiraPlugin miraPlugin, PluginInfo info)
public class MiraPluginInfo(IMiraPlugin miraPlugin, PluginInfo info)
{
public string PluginId { get; } = id;
public IMiraPlugin MiraPlugin { get; } = miraPlugin;
public PluginInfo PluginInfo { get; } = info;
public ConfigFile PluginConfig { get; } = miraPlugin.GetConfigFile();
public string PluginId { get; set; } = info.Metadata.GUID;
public IMiraPlugin MiraPlugin { get; set; } = miraPlugin;
public PluginInfo PluginInfo { get; set; } = info;
public ConfigFile PluginConfig { get; set; } = miraPlugin.GetConfigFile();
public readonly List<AbstractOptionGroup> OptionGroups = [];
public readonly List<IModdedOption> Options = [];
public readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
public readonly Dictionary<int, CustomGameMode> GameModes = [];
internal readonly Dictionary<int, CustomGameMode> GameModes = [];
}
+63 -37
View File
@@ -1,16 +1,17 @@
using BepInEx;
using BepInEx.Unity.IL2CPP;
using Il2CppInterop.Runtime.Injection;
using BepInEx.Unity.IL2CPP;
using MiraAPI.GameOptions;
using MiraAPI.GameOptions.Attributes;
using MiraAPI.Hud;
using MiraAPI.Modifiers;
using MiraAPI.Roles;
using MiraAPI.Utilities.Colors;
using Reactor.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using MiraAPI.Colors;
using MiraAPI.Utilities;
using Reactor.Networking;
namespace MiraAPI.PluginLoading;
@@ -29,26 +30,26 @@ internal class MiraPluginManager
internal void Initialize()
{
Instance = this;
IL2CPPChainloader.Instance.PluginLoad += (_, assembly, plugin) =>
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
{
if (!plugin.GetType().GetInterfaces().Contains(typeof(IMiraPlugin)))
{
return;
}
var id = MetadataHelper.GetMetadata(plugin.GetType()).GUID;
var info = new MiraPluginInfo(id, plugin as IMiraPlugin, IL2CPPChainloader.Instance.Plugins[id]);
var info = new MiraPluginInfo(plugin as IMiraPlugin, pluginInfo);
RegisterModifierAttribute(assembly);
RegisterAllOptions(assembly, info);
RegisterRoleAttribute(assembly, info);
RegisterButtonAttribute(assembly);
RegisterAllColors(assembly);
RegisterColorClasses(assembly);
RegisteredPlugins.Add(assembly, info);
Logger<MiraApiPlugin>.Info($"Registering mod {id} with Mira API.");
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
};
}
@@ -67,7 +68,7 @@ internal class MiraPluginManager
{
continue;
}
foreach (var property in type.GetProperties())
{
if (property.PropertyType.IsAssignableTo(typeof(IModdedOption)))
@@ -75,7 +76,7 @@ internal class MiraPluginManager
ModdedOptionsManager.RegisterPropertyOption(type, property, pluginInfo);
continue;
}
var attribute = property.GetCustomAttribute<ModdedOptionAttribute>();
if (attribute == null)
{
@@ -89,6 +90,7 @@ internal class MiraPluginManager
private static void RegisterRoleAttribute(Assembly assembly, MiraPluginInfo pluginInfo)
{
List<Type> roles = [];
foreach (var type in assembly.GetTypes())
{
var attribute = type.GetCustomAttribute<RegisterCustomRoleAttribute>();
@@ -96,48 +98,72 @@ internal class MiraPluginManager
{
continue;
}
ClassInjector.RegisterTypeInIl2Cpp(type);
var role = CustomRoleManager.RegisterRole(type, attribute.RoleId, pluginInfo);
try
if (!(typeof(RoleBehaviour).IsAssignableFrom(type) && typeof(ICustomRole).IsAssignableFrom(type)))
{
pluginInfo.CustomRoles.Add((ushort)role.Role, role);
Logger<MiraApiPlugin>.Error($"{type.Name} does not inherit from RoleBehaviour or ICustomRole.");
continue;
}
catch (Exception e)
if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients)
{
Logger<MiraApiPlugin>.Error("Failed to register role: " + type.Name);
foreach (var (k, v) in pluginInfo.CustomRoles)
{
Logger<MiraApiPlugin>.Error($"{k}: {v}");
}
Logger<MiraApiPlugin>.Error(e);
Logger<MiraApiPlugin>.Error("Custom roles are only supported on all clients.");
return;
}
roles.Add(type);
}
CustomRoleManager.RegisterRoleTypes(roles, pluginInfo);
}
private static void RegisterAllColors(Assembly assembly)
private static void RegisterColorClasses(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
foreach (FieldInfo field in type.GetFields())
if (type.GetCustomAttribute<RegisterCustomColorsAttribute>() == null)
{
foreach (var attribute in field.GetCustomAttributes())
continue;
}
if (!type.IsStatic())
{
Logger<MiraApiPlugin>.Error($"Color class {type.Name} must be static.");
continue;
}
foreach (var property in type.GetProperties())
{
if (property.PropertyType != typeof(CustomColor))
{
if (attribute.GetType().BaseType == typeof(RegisterColorAttribute))
{
/* Palette.PlayerColors = Palette.PlayerColors.ToArray().AddToArray();
Palette.ShadowColors = Palette.ShadowColors.ToArray().AddToArray();
Palette.ColorNames = Palette.ColorNames.ToArray().AddToArray();*/
}
continue;
}
var color = (CustomColor)property.GetValue(null);
if (color == null)
{
Logger<MiraApiPlugin>.Error($"Color property {property.Name} in {type.Name} is null.");
continue;
}
PaletteManager.CustomColors.Add(color);
}
}
}
private static void RegisterModifierAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
{
var attribute = type.GetCustomAttribute<RegisterModifierAttribute>();
if (attribute != null)
{
ModifierManager.RegisterModifier(type);
}
}
}
private static void RegisterButtonAttribute(Assembly assembly)
{
foreach (var type in assembly.GetTypes())
Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+67 -47
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,7 +11,6 @@ using Reactor.Utilities.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using HarmonyLib;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
@@ -20,23 +21,39 @@ public static class CustomRoleManager
{
public static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = new();
private static int GetNextRoleId(ushort requestedId)
{
while (CustomRoles.ContainsKey(requestedId) || requestedId <= Enum.GetNames<RoleTypes>().Length)
{
Logger<MiraApiPlugin>.Error(requestedId + ", " + (requestedId+1));
requestedId++;
}
public static readonly Dictionary<Type, ushort> RoleIds = new();
return requestedId;
private static ushort _roleId = 100;
private static ushort GetNextRoleId()
{
return _roleId++;
}
public static void RegisterInRoleManager()
internal static void RegisterInRoleManager()
{
RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray();
}
internal static RoleBehaviour RegisterRole(Type roleType, ushort roleId, MiraPluginInfo parentMod)
public static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo)
{
roles.ForEach(x => RoleIds.Add(x, GetNextRoleId()));
foreach (var roleType in roles)
{
ClassInjector.RegisterTypeInIl2Cpp(roleType);
var role = RegisterRole(roleType, pluginInfo);
if (role is null)
{
continue;
}
pluginInfo.CustomRoles.Add((ushort)role.Role, role);
}
}
private static RoleBehaviour RegisterRole(Type roleType, MiraPluginInfo parentMod)
{
if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType)))
{
@@ -52,7 +69,9 @@ public static class CustomRoleManager
return null;
}
roleBehaviour.Role = (RoleTypes)GetNextRoleId(roleId);
var roleId = RoleIds[roleType];
roleBehaviour.Role = (RoleTypes)roleId;
roleBehaviour.TeamType = customRole.Team == ModdedRoleTeams.Neutral ? RoleTeamTypes.Crewmate : (RoleTeamTypes)customRole.Team;
roleBehaviour.NameColor = customRole.RoleColor;
roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName);
@@ -64,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)
@@ -94,7 +113,7 @@ public static class CustomRoleManager
public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole result)
{
CustomRoles.TryGetValue((ushort)roleType, out var temp);
if (temp != null)
if (temp)
{
result = temp as ICustomRole;
return true;
@@ -104,7 +123,7 @@ public static class CustomRoleManager
return false;
}
public static TaskPanelBehaviour CreateRoleTab(ICustomRole role)
internal static TaskPanelBehaviour CreateRoleTab(ICustomRole role)
{
var ogPanel = HudManager.Instance.TaskStuff.transform.FindChild("TaskPanel").gameObject.GetComponent<TaskPanelBehaviour>();
var clonePanel = Object.Instantiate(ogPanel.gameObject, ogPanel.transform.parent);
@@ -122,7 +141,7 @@ public static class CustomRoleManager
return newPanel;
}
public static void UpdateRoleTab(TaskPanelBehaviour panel, ICustomRole role)
internal static void UpdateRoleTab(TaskPanelBehaviour panel, ICustomRole role)
{
var tabText = panel.tab.gameObject.GetComponentInChildren<TextMeshPro>();
var ogPanel = HudManager.Instance.TaskStuff.transform.FindChild("TaskPanel").gameObject.GetComponent<TaskPanelBehaviour>();
@@ -137,42 +156,32 @@ public static class CustomRoleManager
panel.SetTaskText(role.SetTabText().ToString());
}
public static void SyncAllRoleSettings(int targetId=-1)
internal static void SyncAllRoleSettings(int targetId = -1)
{
List<NetData> data = [];
int count = 0;
foreach (var role in CustomRoles.Values)
var data = CustomRoles.Values
.Where(x => x is ICustomRole { HideSettings: false })
.Select(x => ((ICustomRole)x).GetNetData())
.ChunkNetData(1000);
while (data.Count > 0)
{
ICustomRole customRole = role as ICustomRole;
if (customRole is null or { HideSettings: true })
{
continue;
}
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.NumConfigDefinition, out var numEntry);
customRole.ParentMod.PluginConfig.TryGetEntry<int>(customRole.ChanceConfigDefinition, out var chanceEntry);
var netData = new NetData((uint)role.Role, BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
data.Add(netData);
count += netData.GetLength();
if (count > 1000)
{
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
data.Clear();
count = 0;
}
}
if (data.Count > 0)
{
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.ToArray());
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(PlayerControl.LocalPlayer, targetId, data.Dequeue());
}
}
public static void HandleSyncRoleOptions(NetData[] data)
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))
@@ -188,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,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();
}
}
}
+26 -12
View File
@@ -1,12 +1,13 @@
using System;
using AmongUs.GameOptions;
using AmongUs.GameOptions;
using BepInEx.Configuration;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using System.Text;
using HarmonyLib;
using MiraAPI.Modifiers;
using MiraAPI.Networking;
using MiraAPI.PluginLoading;
using MiraAPI.Utilities;
using MiraAPI.Utilities.Assets;
using System;
using System.Text;
using UnityEngine;
namespace MiraAPI.Roles;
@@ -23,6 +24,8 @@ public interface ICustomRole
ModdedRoleTeams Team { get; }
int MaxPlayers => 15;
LoadableAsset<Sprite> OptionsScreenshot => MiraAssets.Empty;
LoadableAsset<Sprite> Icon => MiraAssets.Empty;
@@ -45,8 +48,6 @@ public interface ICustomRole
bool IsGhostRole => false;
bool TargetsBodies => false;
bool CreateCustomTab => true;
bool HideSettings => IsGhostRole;
@@ -54,9 +55,17 @@ 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>
/// <param name="playerControl">The PlayerControl that has this role</param>
void PlayerControlFixedUpdate(PlayerControl playerControl) { }
/// <summary>
/// Only runs for local player on HudManager Update method
/// </summary>
/// <param name="hudManager">Reference to HudManager instance</param>
void HudUpdate(HudManager hudManager) { }
string GetCustomEjectionMessage(NetworkedPlayerInfo player)
@@ -69,12 +78,17 @@ public interface ICustomRole
var taskStringBuilder = Helpers.CreateForRole(this);
return taskStringBuilder;
}
NetData GetNetData(RoleBehaviour roleBehaviour)
bool IsModifierApplicable(BaseModifier modifier)
{
return true;
}
NetData GetNetData()
{
ParentMod.PluginConfig.TryGetEntry<int>(NumConfigDefinition, out var numEntry);
ParentMod.PluginConfig.TryGetEntry<int>(ChanceConfigDefinition, out var chanceEntry);
return new NetData((uint)roleBehaviour.Role, BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
return new NetData(RoleId.Get(GetType()), BitConverter.GetBytes(numEntry.Value).AddRangeToArray(BitConverter.GetBytes(chanceEntry.Value)));
}
}
+1 -13
View File
@@ -3,16 +3,4 @@
namespace MiraAPI.Roles;
[AttributeUsage(AttributeTargets.Class)]
public class RegisterCustomRoleAttribute : Attribute
{
public ushort RoleId { get; }
public RegisterCustomRoleAttribute()
{
RoleId = (ushort)(20 + CustomRoleManager.CustomRoles.Count);
}
public RegisterCustomRoleAttribute(ushort roleId)
{
RoleId = roleId;
}
}
public class RegisterCustomRoleAttribute : Attribute;
+26
View File
@@ -0,0 +1,26 @@
using System;
namespace MiraAPI.Roles;
public static class RoleId
{
public static ushort Get<T>() where T : RoleBehaviour
{
if (!CustomRoleManager.RoleIds.TryGetValue(typeof(T), out var roleId))
{
throw new InvalidOperationException($"Role {typeof(T)} is not registered");
}
return roleId;
}
public static ushort Get(Type type)
{
if (!CustomRoleManager.RoleIds.TryGetValue(type, out var roleId))
{
throw new InvalidOperationException($"Role {type} is not registered");
}
return roleId;
}
}
@@ -1,4 +1,5 @@
using System.Reflection;
using Reactor.Utilities;
using UnityEngine;
namespace MiraAPI.Utilities.Assets;
@@ -9,7 +10,7 @@ public class LoadableResourceAsset(string path) : LoadableAsset<Sprite>
public override Sprite LoadAsset()
{
if (LoadedAsset != null)
if (LoadedAsset)
{
return LoadedAsset;
}
@@ -20,7 +21,7 @@ public class LoadableResourceAsset(string path) : LoadableAsset<Sprite>
}
catch
{
Debug.LogError($"Not loading, invalid asset: {path}");
Logger<MiraApiPlugin>.Error($"Not loading, invalid asset: {path}");
return null;
}
}
+8 -1
View File
@@ -1,4 +1,6 @@
namespace MiraAPI.Utilities.Assets;
using UnityEngine;
namespace MiraAPI.Utilities.Assets;
public static class MiraAssets
{
@@ -7,4 +9,9 @@ public static class MiraAssets
public static readonly LoadableResourceAsset NextButton = new("MiraAPI.Resources.NextButton.png");
public static readonly LoadableResourceAsset NextButtonActive = new("MiraAPI.Resources.NextButtonActive.png");
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);
}
+5 -5
View File
@@ -13,17 +13,17 @@ namespace MiraAPI.Utilities.Assets;
/// </summary>
public static class SpriteTools
{
private delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
public delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
private static DLoadImage _iCallLoadImage;
public static DLoadImage ICallLoadImage;
private static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
public static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
{
_iCallLoadImage ??= IL2CPP.ResolveICall<DLoadImage>("UnityEngine.ImageConversion::LoadImage");
ICallLoadImage ??= IL2CPP.ResolveICall<DLoadImage>("UnityEngine.ImageConversion::LoadImage");
var il2CPPArray = (Il2CppStructArray<byte>)data;
return _iCallLoadImage.Invoke(tex.Pointer, il2CPPArray.Pointer, markNonReadable);
return ICallLoadImage.Invoke(tex.Pointer, il2CPPArray.Pointer, markNonReadable);
}
/// <summary>
-30
View File
@@ -1,30 +0,0 @@
using UnityEngine;
namespace MiraAPI.Utilities;
public class CustomColor
{
public CustomColor(Color32 mainColor, StringNames name)
{
MainColor = mainColor;
ShadowColor = GetShadowColor(mainColor, 60);
Name = name;
}
public CustomColor(Color32 mainColor, Color32 shadowColor, StringNames name)
{
MainColor = mainColor;
ShadowColor = shadowColor;
Name = name;
}
public Color32 MainColor { get; }
public Color32 ShadowColor { get; }
public StringNames Name { get; }
public static Color32 GetShadowColor(Color32 c, byte darknessAmount)
{
return
new Color32((byte)Mathf.Clamp(c.r - darknessAmount, 0, 255), (byte)Mathf.Clamp(c.g - darknessAmount, 0, 255),
(byte)Mathf.Clamp(c.b - darknessAmount, 0, 255), byte.MaxValue);
}
}
@@ -1,6 +0,0 @@
using System;
namespace MiraAPI.Utilities.Colors;
[AttributeUsage(AttributeTargets.Field)]
public class RegisterColorAttribute : Attribute;
+165 -73
View File
@@ -1,6 +1,10 @@
using MiraAPI.GameOptions;
using MiraAPI.Roles;
using Reactor.Utilities.Extensions;
#nullable enable
using MiraAPI.GameOptions;
using MiraAPI.Modifiers;
using MiraAPI.Networking;
using Reactor.Networking.Attributes;
using Reactor.Utilities;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
@@ -8,103 +12,191 @@ namespace MiraAPI.Utilities;
public static class Extensions
{
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());
}
return chunks;
}
public static bool IsCustom(this OptionBehaviour optionBehaviour)
{
return ModdedOptionsManager.ModdedOptions.Values.Any(opt => opt.OptionBehaviour && opt.OptionBehaviour.Equals(optionBehaviour));
}
public static Color DarkenColor(this Color color)
public static readonly Dictionary<PlayerControl, ModifierComponent> ModifierComponents = new();
public static ModifierComponent? GetModifierComponent(this PlayerControl player)
{
return new Color(color.r - 0.3f, color.g - 0.3f, color.b - 0.3f);
}
public static Color GetAlternateColor(this Color color)
{
return color.IsColorDark() ? LightenColor(color) : DarkenColor(color);
if (ModifierComponents.TryGetValue(player, out var component))
{
return component;
}
component = player.GetComponent<ModifierComponent>();
if (component)
{
ModifierComponents[player] = component;
}
return component;
}
public static Color LightenColor(this Color color)
public static List<T> Randomize<T>(this List<T> list)
{
return new Color(color.r + 0.3f, color.g + 0.3f, color.b + 0.3f);
List<T> randomizedList = [];
System.Random rnd = new();
while (list.Count > 0)
{
var index = rnd.Next(0, list.Count);
randomizedList.Add(list[index]);
list.RemoveAt(index);
}
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);
}
[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))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
player.RpcAddModifier(id);
}
public static void RpcRemoveModifier<T>(this PlayerControl player) where T : BaseModifier
{
if (!ModifierManager.TypeToIdModifiers.TryGetValue(typeof(T), out var id))
{
Logger<MiraApiPlugin>.Error($"Cannot add modifier {typeof(T).Name} because it is not registered.");
return;
}
player.RpcAddModifier(id);
}
public static Color DarkenColor(this Color color, float amount = 0.45f)
{
return new Color(color.r - amount, color.g - amount, color.b - amount);
}
public static Color GetAlternateColor(this Color color, float amount = 0.45f)
{
return color.IsColorDark() ? LightenColor(color, amount) : DarkenColor(color, amount);
}
public static Color LightenColor(this Color color, float amount = 0.45f)
{
return new Color(color.r + amount, color.g + amount, color.b + amount);
}
public static bool IsColorDark(this Color color)
{
return color.r < 0.5f && color.g < 0.5f && color.b < 0.5f;
return color.r < 0.5f && color is { g: < 0.5f, b: < 0.5f };
}
public static void UpdateBodies(this PlayerControl playerControl, Color outlineColor, ref DeadBody target)
public static DeadBody? GetNearestDeadBody(this PlayerControl playerControl, float radius)
{
foreach (var body in Object.FindObjectsOfType<DeadBody>())
{
foreach (var bodyRenderer in body.bodyRenderers)
{
bodyRenderer.SetOutline(null);
}
}
if (playerControl.Data.Role is not ICustomRole { TargetsBodies: true })
{
return;
}
target = playerControl.NearestDeadBody();
if (!target)
{
return;
}
foreach (var renderer in target.bodyRenderers)
{
renderer.SetOutline(outlineColor);
}
return Helpers.GetNearestDeadBodies(playerControl.GetTruePosition(), radius).FirstOrDefault(component => component && !component.Reported);
}
public static DeadBody NearestDeadBody(this PlayerControl playerControl)
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(), playerControl.MaxReportDistance / 4f, Helpers.Filter, results);
return results.ToArray()
.Where(collider2D => collider2D.CompareTag("DeadBody"))
.Select(collider2D => collider2D.GetComponent<DeadBody>())
.FirstOrDefault(component => component && !component.Reported);
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));
foreach (var playerInfo in GameData.Instance.AllPlayers)
{
if (playerInfo.Disconnected || playerInfo.PlayerId == playerControl.PlayerId ||
playerInfo.IsDead || !includeImpostors && playerInfo.Role.IsImpostor)
{
continue;
}
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();
}
}
+132 -6
View File
@@ -1,31 +1,157 @@
using MiraAPI.Roles;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
namespace MiraAPI.Utilities;
public static class Helpers
{
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue);
public static PlainShipRoom GetRoom(Vector3 pos)
{
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)
{
var textObj = new GameObject(name)
{
transform =
{
parent = parent
},
layer = LayerMask.NameToLayer("UI")
};
var textMeshPro = textObj.AddComponent<TextMeshPro>();
textMeshPro.fontSize = fontSize;
textMeshPro.alignment = textAlignment;
textMeshPro.font = HudManager.Instance.TaskPanel.taskText.font;
textMeshPro.fontMaterial = HudManager.Instance.TaskPanel.taskText.fontMaterial;
var aspectPosition = textObj.AddComponent<AspectPosition>();
aspectPosition.Alignment = alignment;
aspectPosition.DistanceFromEdge = distance;
aspectPosition.AdjustPosition();
return textMeshPro;
}
public static DeadBody GetBodyById(byte id)
{
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
}
+117 -6
View File
@@ -6,32 +6,82 @@ A thorough, but simple, Among Us modding API and utility library that covers:
- Roles
- Options
- Modifiers
- Game Modes
- Buttons
- Custom Colors
- Assets
- HUD Elements
- Compatibility
- ~~Game Modes~~ (coming soon)
Mira API strives to be simple and easy to use, while also using as many base game elements as possible. The result is a less intrusive, better modding API that covers general use cases.
**Join the [Discord](https://discord.gg/all-of-us-launchpad-794950428756410429) for support and to stay updated on the latest releases**
# Usage
To start using Mira API, you need to:
1. Add a reference to Mira API either through a DLL, project reference, or NuGet package.
1. Add a reference to Mira API either through a [DLL](https://github.com/All-Of-Us-Mods/MiraAPI/releases), project reference, or [NuGet package](https://www.nuget.org/packages/AllOfUs.MiraAPI).
2. Add a BepInDependency on your plugin class like this: `[BepInDependency(MiraApiPlugin.Id)]`
3. Implement the IMiraPlugin interface on your plugin class.
For a full example, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExamplePlugin.cs).
## Recommended Project Structure
It is highly recommended to follow this project structure when using Mira API in order to keep your code clean and organized. You can also view the Example Mod in this repository for some guidance.
```
MyMiraMod/
├── Buttons/
│ └── MyCoolButton.cs
├── Options/
│ ├── Roles/
│ │ └── CoolCustomRoleOptions.cs
│ └── MainOptionGroup.cs
├── Patches/
│ ├── Roles/
│ │ └── CoolCustomRole/
│ │ ├── PlayerControlPatches.cs
│ │ └── ExileControllerPatches.cs
│ └── General/
│ └── HudManagerPatches.cs
├── Resources/
│ ├── CoolButton.png
│ └── myAssets-win-x86.bundle
├── Roles/
│ └── CoolCustomRole.cs
├── MyMiraModPlugin.cs
└── MyModAssets.cs
```
## Roles
Roles are very simple in Mira API. There are 3 things you need to do to create a custom role:
1. Create a class that inherits from a base game role (like `RoleBehaviour`, `CrewmateRole`, `ImpostorRole`, etc).
1. Create a class that inherits from a base game role (like `CrewmateRole`, `ImpostorRole`, etc)
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.
See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Roles/CustomRole.cs) for a code example.
## Modifiers
Mira API uses a different definition of 'modifiers' than other Among Us mods. For example, in Town Of Us, a modifier is an extra "ability" that is applied on top of the base role. However, in Mira API, modifiers are very flexible. A modifier is anything that "modifiers" a player's abilities or interactions.
Mira provides 3 classes for working with modifiers:
- `BaseModifier`: The base for every modifier. You MUST add and remove this modifier from a player manually!
- `TimedModifier`: A modifier that has a time limit. This modifier has to be added manually, but will automatically remove itself after the time limit
- `GameModifier`: This works like the typical TOU modifier, where it is automatically applied at the beginning of the game, then removed at the end.
Modifiers provide various overridable functions and properties for custom behaviour. They can also be used for "tagging" a player. You can check if a player has a modifier through the extension method `HasModifier` on a `PlayerControl` object.
To start using a modifier, pick one of the base classes above and create a class that inherits from it. Implement the properties and methods you would like, then add the `[RegisterModifier]` attribute to the class.
An example Game modifier can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Modifiers/GameModifierExample.cs).
An example Timer modifier can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Modifiers/ModifierTimerExample.cs).
## Options
Options are also very simple in Mira API. Options are split up into Groups and Options. Every Option needs to be in a Group.
Options are also very simple in Mira API. Mira API handles all the hard work behind the scenes, so developers only have to follow a few steps to create their custom options. The Options API is split up into Groups and Options. Every Option needs to be in a Group.
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class. Groups contain 4 properties, `GroupName`, `GroupColor`, `GroupVisible`, and `AdvancedRole`. Only the `GroupName` is required.
@@ -50,13 +100,15 @@ You can access any group class using the `OptionGroupSingleton` class like this:
```csharp
// MyOptionsGroup is a class that inherits from AbstractOptionGroup
var myGroup = OptionGroupSingleton<MyOptionsGroup>.Instance; // gets the instance of the group
System.Out.Console.WriteLine(myGroup.MyNumberOption); // prints the value of the option to the console
Logger<MyPlugin>.Info(myGroup.MyNumberOption); // prints the value of the option to the console
```
Once you have an options group, there are two ways to make the actual options:
- Use an Option Attribute with a property.
- Create a ModdedOption property.
### Option Attributes
This is an example of using an Option Attribute on a property:
```csharp
// The first parameter is always the name of the option. The rest are dependent on the type of option.
@@ -64,6 +116,26 @@ This is an example of using an Option Attribute on a property:
public float SussyLevel { get; set; } = 4f; // You can set a default value here.
```
Here are the available Option Attributes and their signatures:
```csharp
ModdedEnumOption(string name, Type enumType, Type roleType = null)
ModdedNumberOption(
string name,
float min,
float max,
float increment=1
NumberSuffixes suffixType = NumberSuffixes.None,
bool zeroInfinity = false,
Type roleType = null)
ModdedStringOption(string title, string[] values, Type roleType = null)
ModdedToggleOption(string name, Type roleType = null)
```
### ModdedOption Properties
And this is an example of a ModdedOption property:
```csharp
public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezus Ability", false);
@@ -108,16 +180,55 @@ 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:
1. Create a class that inherits from the `CustomActionButton` class and implement the properties and methods.
2. Add the `[RegisterCustomButton]` attribute to the class.
All other tasks and logic required to add the button to the game are handled by Mira API.
In case you need to access your `CustomActionButton` instance from another class, you can use the `CustomButtonSingleton` class like this:
```csharp
var myButton = CustomButtonSingleton<MyCoolButton>.Instance;
```
The button API is simple, but provides a lot of flexibility. There are various methods you can override to customize the behaviour of your button. See [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI/Hud/CustomActionButton.cs) for a full list of methods you can override.
An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Buttons/ExampleButton.cs).
## Custom Colors
Mira provides a simple Custom Color API that allows you to add custom player colors to the game.
Creating custom colors isn't difficult, but there are some requirements for your colors to be registered by Mira.
1. Create a ***STATIC*** class to house all your `CustomColor` objects.
2. Inside this class, create a `CustomColor` property for each color you intend to add.
3. Add the `[RegisterCustomColors]` attribute to the class.
Here is an example of a custom color class:
```csharp
[RegisterCustomColors]
public static class MyCustomColors
{
public static CustomColor Cerulean { get; } = new CustomColor("Cerulean", new Color(0.0f, 0.48f, 0.65f));
public static CustomColor Rose { get; } = new CustomColor("Rose", new Color(0.98f, 0.26f, 0.62f));
public static CustomColor Gold { get; } = new CustomColor("Gold", new Color(1.0f, 0.84f, 0.0f));
}
```
## Assets
Mira API provides a simple, but expandable asset system. The core of the system is the `LoadableAsset<T>` class. This is a generic abstract class that provides a pattern for loading assets.
+28
View File
@@ -0,0 +1,28 @@
var target = Argument("target", "Build");
var workflow = BuildSystem.GitHubActions.Environment.Workflow;
var buildId = workflow.RunNumber;
var tag = workflow.RefType == GitHubActionsRefType.Tag ? workflow.RefName : null;
Task("Build")
.Does(() =>
{
var settings = new DotNetBuildSettings
{
Configuration = "Release",
MSBuildSettings = new DotNetMSBuildSettings()
};
if (tag != null)
{
settings.MSBuildSettings.Version = tag;
}
else if (buildId != 0)
{
settings.MSBuildSettings.VersionSuffix = "ci." + buildId;
}
DotNetBuild(".", settings);
});
RunTarget(target);
+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>