mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Compare commits
53
Commits
0.2.0-rc.7
...
commission
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d7d9028433 | ||
|
|
05f132c506 | ||
|
|
ab54c44434 | ||
|
|
24b52ededc | ||
|
|
bf652b68c3 | ||
|
|
00377c7926 | ||
|
|
55555604f3 | ||
|
|
de77a9fa92 | ||
|
|
eba2a037d3 | ||
|
|
bc7f772de9 | ||
|
|
f43191c4f2 | ||
|
|
56d4e399d9 | ||
|
|
0d41ebbc13 | ||
|
|
9388ff487a | ||
|
|
720491e5cf | ||
|
|
9884df356e | ||
|
|
5dbcc1435d | ||
|
|
7ec4addacc | ||
|
|
bc9bc0ce24 | ||
|
|
aff1c311fa | ||
|
|
0d2a9d9700 | ||
|
|
79359ec67d | ||
|
|
669c541279 | ||
|
|
0479ff96b1 | ||
|
|
4e314a0a7e | ||
|
|
2e5f9639b3 | ||
|
|
e6d1f440c8 | ||
|
|
2ac8bdc240 | ||
|
|
d9cc57d355 | ||
|
|
8fc5137e87 | ||
|
|
d5d99001ea | ||
|
|
16ff2f8492 | ||
|
|
f943277e38 | ||
|
|
dbb17a4cd7 | ||
|
|
e83c5e504d | ||
|
|
ed0b4ebbc4 | ||
|
|
9077a13b5d | ||
|
|
fb3eabfb29 | ||
|
|
df87039884 | ||
|
|
a51369a4a6 | ||
|
|
23fe7e6feb | ||
|
|
e2f387a3f6 | ||
|
|
96f84b741c | ||
|
|
b8d24c28e1 | ||
|
|
84e3f4085e | ||
|
|
efb19cf46f | ||
|
|
57a31b7761 | ||
|
|
0f5add5251 | ||
|
|
7450c73243 | ||
|
|
bc188191de | ||
|
|
f5b4bdd1d4 | ||
|
|
cd6c4f5390 | ||
|
|
5cc29d903a |
No files matched your search
+6
-3
@@ -1,17 +1,20 @@
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Example.Extension.GameOver;
|
||||
using MiraAPI.Example.Extension.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
namespace MiraAPI.Example.Extension.Buttons;
|
||||
|
||||
public class NeutralKillerButton : CustomActionButton
|
||||
{
|
||||
public override string Name => "Win Game";
|
||||
|
||||
public override float Cooldown => 0f;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using BepInEx;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
using Reactor;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Networking.Attributes;
|
||||
|
||||
namespace MiraAPI.Example.Extension;
|
||||
|
||||
[BepInAutoPlugin("mira.example.extension", "MiraExampleExtensionMod")]
|
||||
[BepInProcess("Among Us.exe")]
|
||||
[BepInDependency(ReactorPlugin.Id)]
|
||||
[BepInDependency(MiraApiPlugin.Id)]
|
||||
[BepInDependency(ExamplePlugin.Id)]
|
||||
[ReactorModFlags(ModFlags.RequireOnAllClients)]
|
||||
public partial class ExampleExtensionPlugin : BasePlugin, IMiraExtension
|
||||
{
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
Harmony.PatchAll();
|
||||
}
|
||||
|
||||
public Type GetBasePluginType()
|
||||
{
|
||||
return typeof(ExamplePlugin);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -1,9 +1,9 @@
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.Example.Extension.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.GameOver;
|
||||
namespace MiraAPI.Example.Extension.GameOver;
|
||||
|
||||
public class NeutralKillerGameOver : CustomGameOver
|
||||
{
|
||||
@@ -0,0 +1,14 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0</Version>
|
||||
<Description>Example extension for Mira API</Description>
|
||||
<Authors>Angxl, XtraCube</Authors>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\MiraAPI\MiraAPI.csproj" />
|
||||
<ProjectReference Include="..\MiraAPI.Example\MiraAPI.Example.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<Import Project="..\AmongUs.props" />
|
||||
</Project>
|
||||
@@ -0,0 +1,16 @@
|
||||
using MiraAPI.Example.Extension.Options;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
|
||||
namespace MiraAPI.Example.Extension.Modifiers;
|
||||
|
||||
public class ExtensionModifier : GameModifier
|
||||
{
|
||||
public override string ModifierName => "Extension Modifier";
|
||||
|
||||
public override string GetDescription() => "This is from the extension mod!";
|
||||
|
||||
public override int GetAssignmentChance() => (int)OptionGroupSingleton<ExtensionModifierOptions>.Instance.ModifierChance;
|
||||
|
||||
public override int GetAmountPerGame() => (int)OptionGroupSingleton<ExtensionModifierOptions>.Instance.ModifierLimit;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
|
||||
namespace MiraAPI.Example.Extension.Options;
|
||||
|
||||
public class ExtendedFreezerOptions : AbstractOptionGroup<FreezerRole>
|
||||
{
|
||||
public override string GroupName => "Extended Freezer Role Options";
|
||||
|
||||
[ModdedNumberOption("Freeze Uses", 1, 5)]
|
||||
public float FreezeUses { get; set; } = 1;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using MiraAPI.Example.Extension.Modifiers;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Example.Extension.Options;
|
||||
|
||||
public class ExtensionModifierOptions : AbstractOptionGroup<ExtensionModifier>
|
||||
{
|
||||
public override string GroupName => "Extension Modifier Options";
|
||||
|
||||
[ModdedNumberOption("Modifier Chance", 0, 100, 10, MiraNumberSuffixes.Percent)]
|
||||
public float ModifierChance { get; set; } = 100f;
|
||||
|
||||
[ModdedNumberOption("Modifier Limit", 0, 10)]
|
||||
public float ModifierLimit { get; set; } = 0;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Example.Buttons.Freezer;
|
||||
using MiraAPI.Example.Extension.Options;
|
||||
using MiraAPI.GameOptions;
|
||||
|
||||
namespace MiraAPI.Example.Extension.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(FreezeButton), nameof(FreezeButton.MaxUses), MethodType.Getter)]
|
||||
public static class FreezeButtonPatch
|
||||
{
|
||||
public static void Postfix(ref int __result)
|
||||
{
|
||||
__result = (int)OptionGroupSingleton<ExtendedFreezerOptions>.Instance.FreezeUses;
|
||||
}
|
||||
}
|
||||
+7
-3
@@ -1,16 +1,20 @@
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.Example.Extension.GameOver;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Roles;
|
||||
namespace MiraAPI.Example.Extension.Roles;
|
||||
|
||||
public class NeutralKillerRole : ImpostorRole, ICustomRole
|
||||
{
|
||||
public string RoleName => "Outcast Killer";
|
||||
|
||||
public string RoleDescription => "Outcast who can kill.";
|
||||
public string RoleLongDescription => RoleDescription;
|
||||
|
||||
public string RoleLongDescription => this.RoleDescription;
|
||||
|
||||
public Color RoleColor => Color.magenta;
|
||||
|
||||
public ModdedRoleTeams Team => ModdedRoleTeams.Custom;
|
||||
|
||||
public CustomRoleConfiguration Configuration => new(this)
|
||||
@@ -16,7 +16,7 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
||||
|
||||
public override float Cooldown => OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeDuration;
|
||||
|
||||
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
|
||||
public override int MaxUses => 1;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
|
||||
|
||||
@@ -6,7 +6,10 @@ 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 Cerulean { get; } = new("Cerulean", new Color(0.0f, 0.48f, 0.65f))
|
||||
{
|
||||
ColorBrightness = CustomColorBrightness.Lighter,
|
||||
};
|
||||
|
||||
public static CustomColor Rose { get; } = new("Rose", new Color(0.98f, 0.26f, 0.62f));
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ public static class ExampleEventHandlers
|
||||
[RegisterEvent(15)]
|
||||
public static void HandleVoteEvent(HandleVoteEvent @event)
|
||||
{
|
||||
if (@event.VoteData.Owner.Data.Role is not NeutralKillerRole) return;
|
||||
if (@event.VoteData.Owner.Data.Role is not FreezerRole) return;
|
||||
|
||||
@event.VoteData.SetRemainingVotes(0);
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ public class ExampleOptions2 : AbstractOptionGroup
|
||||
|
||||
public override uint GroupPriority => 0; // This group will be displayed first. The default value is uint.MaxValue.
|
||||
|
||||
public ModdedPlayerOption PlayerOption2 { get; } = new("YOU MUST CHOSE A PLAYER", false);
|
||||
public ModdedPlayerOption PlayerOption { get; } = new("Eh whatever");
|
||||
public ModdedToggleOption ToggleOpt1 { get; } = new("Toggle Option 1", false);
|
||||
|
||||
public ModdedToggleOption ToggleOpt2 { get; } = new("Toggle Option 2", false)
|
||||
|
||||
@@ -11,7 +11,4 @@ public class FreezerRoleSettings : AbstractOptionGroup<FreezerRole>
|
||||
|
||||
[ModdedNumberOption("Freeze Duration", 1, 15, 1, MiraNumberSuffixes.Seconds)]
|
||||
public float FreezeDuration { get; set; } = 5;
|
||||
|
||||
[ModdedNumberOption("Freeze Uses", 1, 5)]
|
||||
public float FreezeUses { get; set; } = 1;
|
||||
}
|
||||
@@ -7,6 +7,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiraAPI", "MiraAPI\MiraAPI.
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example", "MiraAPI.Example\MiraAPI.Example.csproj", "{19B76855-16CC-43CC-A0A6-5A7FFB43F077}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MiraAPI.Example.Extension", "MiraAPI.Example.Extension\MiraAPI.Example.Extension.csproj", "{526FF017-80E1-4094-804D-741AF35AA226}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
@@ -21,6 +23,10 @@ Global
|
||||
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{19B76855-16CC-43CC-A0A6-5A7FFB43F077}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{526FF017-80E1-4094-804D-741AF35AA226}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{526FF017-80E1-4094-804D-741AF35AA226}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
||||
@@ -13,19 +13,24 @@ namespace MiraAPI.Colors;
|
||||
public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 shadowColor)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the main color.
|
||||
/// Gets or sets the main color.
|
||||
/// </summary>
|
||||
public Color32 MainColor { get; } = mainColor;
|
||||
public Color32 MainColor { get; set; } = mainColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the shadow color.
|
||||
/// Gets or sets the shadow color.
|
||||
/// </summary>
|
||||
public Color32 ShadowColor { get; } = shadowColor;
|
||||
public Color32 ShadowColor { get; set; } = shadowColor;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name of the color.
|
||||
/// Gets or sets the name of the color.
|
||||
/// </summary>
|
||||
public StringNames Name { get; } = name;
|
||||
public StringNames Name { get; set; } = name;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets whether the color is lighter or darker. Can be used to help with colorblind stuff.
|
||||
/// </summary>
|
||||
public CustomColorBrightness ColorBrightness { get; set; } = CustomColorBrightness.Darker;
|
||||
|
||||
/// <inheritdoc />
|
||||
public CustomColor(StringNames name, Color32 mainColor) : this(name, mainColor, mainColor.GetShadowColor(60))
|
||||
@@ -42,3 +47,19 @@ public sealed class CustomColor(StringNames name, Color32 mainColor, Color32 sha
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be used to help with colorblind stuff.
|
||||
/// </summary>
|
||||
public enum CustomColorBrightness
|
||||
{
|
||||
/// <summary>
|
||||
/// A lighter color.
|
||||
/// </summary>
|
||||
Lighter,
|
||||
|
||||
/// <summary>
|
||||
/// A darker color.
|
||||
/// </summary>
|
||||
Darker,
|
||||
}
|
||||
@@ -62,9 +62,10 @@ public interface IModdedOption
|
||||
/// <param name="toggleOpt">The ToggleOption template.</param>
|
||||
/// <param name="numberOpt">The NumberOption template.</param>
|
||||
/// <param name="stringOpt">The StringOption template.</param>
|
||||
/// <param name="playerOpt">The PlayerOption template.</param>
|
||||
/// <param name="container">>The Transform container for the option.</param>
|
||||
/// <returns>>The created OptionBehaviour object.</returns>
|
||||
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container);
|
||||
OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value as a float.
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Reflection;
|
||||
using BepInEx.Configuration;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -22,6 +23,7 @@ public static class ModdedOptionsManager
|
||||
private static readonly Dictionary<PropertyInfo, ModdedOptionAttribute> OptionAttributes = [];
|
||||
private static readonly Dictionary<Type, AbstractOptionGroup> TypeToGroup = [];
|
||||
|
||||
internal static readonly Dictionary<OptionBehaviour, ModdedPlayerOption> CreatedPlayerOptions = [];
|
||||
internal static readonly Dictionary<uint, IModdedOption> ModdedOptions = [];
|
||||
internal static readonly List<AbstractOptionGroup> Groups = [];
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
|
||||
@@ -135,7 +135,7 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container)
|
||||
{
|
||||
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
|
||||
|
||||
@@ -37,6 +37,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
{
|
||||
if (_parentMod != null || value == null) return;
|
||||
_parentMod = value;
|
||||
|
||||
var entry = _parentMod.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
|
||||
Value = entry.Value;
|
||||
}
|
||||
@@ -177,6 +178,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container);
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// An option for selecting an ingame player. It returns an index of the values list, NOT A PLAYER ID. To get a player out of this, index the Values list.
|
||||
/// </summary>
|
||||
public class ModdedPlayerOption : ModdedOption<int>
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the included players.
|
||||
/// </summary>
|
||||
public List<NetworkedPlayerInfo> Values { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a filter for the players included in the option.
|
||||
/// </summary>
|
||||
public Func<NetworkedPlayerInfo, bool>? PlayerFilter { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the option includes a "None" option. If None is selected, value will return -1.
|
||||
/// </summary>
|
||||
public bool AllowNone { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="allowNone">Whether the option includes a none option. If None is selected, value will return -1.</param>
|
||||
public ModdedPlayerOption(string title, bool allowNone = true) : base(title, allowNone ? -1 : 0, false)
|
||||
{
|
||||
Data = ScriptableObject.CreateInstance<PlayerSelectionGameSetting>();
|
||||
var data = (PlayerSelectionGameSetting)Data;
|
||||
|
||||
data.Title = StringName;
|
||||
data.Type = global::OptionTypes.Player;
|
||||
|
||||
Values = new List<NetworkedPlayerInfo>();
|
||||
AllowNone = allowNone;
|
||||
Value = DefaultValue;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container)
|
||||
{
|
||||
var playerOption = Object.Instantiate(playerOpt, container);
|
||||
|
||||
var filteredList = GetFilteredPlayers();
|
||||
Values = filteredList.ToIl2CppList();
|
||||
|
||||
playerOption.SetUpFromData(Data, 20);
|
||||
playerOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
playerOption.Title = StringName;
|
||||
playerOption.TitleText.text = TranslationController.Instance.GetString(StringName);
|
||||
playerOption.Values = Values;
|
||||
playerOption.Value = Value;
|
||||
playerOption.playerIndex = filteredList.FindIndex(p => p.PlayerId == Value);
|
||||
|
||||
OptionBehaviour = playerOption;
|
||||
ModdedOptionsManager.CreatedPlayerOptions.TryAdd(playerOption, this);
|
||||
|
||||
playerOption.SetValueText();
|
||||
playerOption.AdjustButtonsActiveState();
|
||||
|
||||
return playerOption;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a player ran through the filter.
|
||||
/// </summary>
|
||||
/// <returns>A list of filtered players.</returns>
|
||||
public System.Collections.Generic.List<NetworkedPlayerInfo> GetFilteredPlayers()
|
||||
{
|
||||
return GameData.Instance.AllPlayers.ToArray()
|
||||
.Where(x => PlayerFilter?.Invoke(x) ?? true)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a player from the value. If AllowNone is enabled and none is enabled this will return null.
|
||||
/// </summary>
|
||||
/// <returns>A player or null if chosen none.</returns>
|
||||
public NetworkedPlayerInfo? GetPlayerValue()
|
||||
{
|
||||
return Value < 0 ? null : Values[Value];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
return Value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override NetData GetNetData()
|
||||
{
|
||||
return new NetData(Id, BitConverter.GetBytes(Value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleNetData(byte[] data)
|
||||
{
|
||||
SetValue(BitConverter.ToInt32(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return optionBehaviour.GetInt();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(int newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(newValue), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is PlayerOption opt)
|
||||
{
|
||||
opt.Value = newValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, Transform container)
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
|
||||
|
||||
|
||||
@@ -184,7 +184,10 @@ public abstract class CustomActionButton
|
||||
|
||||
this.Location = location;
|
||||
|
||||
if (!moveButton) return;
|
||||
if (!moveButton)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (HudManagerPatches.BottomLeft == null || HudManagerPatches.BottomRight == null)
|
||||
{
|
||||
|
||||
@@ -1,114 +0,0 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Simple input box for user input.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class InputBox : MonoBehaviour
|
||||
{
|
||||
public InputField inputField = null!;
|
||||
|
||||
public void CreateDialog(string title, string placeholder, string submitText, Action<string> onSubmit)
|
||||
{
|
||||
var canvasObj = new GameObject("DialogCanvas");
|
||||
canvasObj.transform.SetParent(transform);
|
||||
canvasObj.layer = LayerMask.NameToLayer("UI");
|
||||
|
||||
var canvas = canvasObj.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.overrideSorting = true;
|
||||
canvas.sortingOrder = 1000; // Ensure the canvas is on top of other UI elements
|
||||
|
||||
canvasObj.AddComponent<CanvasScaler>().uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
canvasObj.AddComponent<GraphicRaycaster>();
|
||||
|
||||
var panelObj = new GameObject("DialogPanel");
|
||||
panelObj.transform.SetParent(canvasObj.transform);
|
||||
var panelRect = panelObj.AddComponent<RectTransform>();
|
||||
var img = panelObj.AddComponent<Image>();
|
||||
|
||||
img.color = new Color(1, 1, 1, 0.9f);
|
||||
img.raycastTarget = true;
|
||||
img.sprite = MiraAssets.RoundedBox.LoadAsset();
|
||||
img.type = Image.Type.Sliced;
|
||||
|
||||
panelRect.sizeDelta = new Vector2(300, 200);
|
||||
panelRect.anchoredPosition = Vector2.zero;
|
||||
|
||||
var titleText = CreateText(panelObj, title);
|
||||
titleText.alignment = TextAnchor.MiddleCenter;
|
||||
titleText.fontSize = 26;
|
||||
titleText.fontStyle = FontStyle.Bold;
|
||||
titleText.color = Color.white;
|
||||
|
||||
var titleRect = titleText.GetComponent<RectTransform>();
|
||||
titleRect.sizeDelta = new Vector2(280, 50);
|
||||
titleRect.anchoredPosition = new Vector2(0, 70);
|
||||
|
||||
var inputObj = new GameObject("InputField");
|
||||
inputObj.transform.SetParent(panelObj.transform);
|
||||
var inputRect = inputObj.AddComponent<RectTransform>();
|
||||
inputRect.sizeDelta = new Vector2(220, 40);
|
||||
inputRect.anchoredPosition = new Vector2(0, 20);
|
||||
|
||||
var inputBg = inputObj.AddComponent<Image>();
|
||||
inputBg.color = new Color(1, 1, 1, 0.8f);
|
||||
|
||||
inputField = inputObj.AddComponent<InputField>();
|
||||
inputField.textComponent = CreateText(inputObj, string.Empty, 18, Color.black);
|
||||
inputField.placeholder = CreateText(inputObj, placeholder, 18, new Color(0.5f, 0.5f, 0.5f));
|
||||
|
||||
var buttonObj = new GameObject("SubmitButton");
|
||||
buttonObj.transform.SetParent(panelObj.transform);
|
||||
var button = buttonObj.AddComponent<Button>();
|
||||
var buttonRect = buttonObj.AddComponent<RectTransform>();
|
||||
buttonRect.sizeDelta = new Vector2(150, 50);
|
||||
buttonRect.anchoredPosition = new Vector2(0, -50);
|
||||
|
||||
var buttonImg = buttonObj.AddComponent<Image>();
|
||||
buttonImg.color = new Color(1f, 1f, 1f, 1f);
|
||||
buttonImg.sprite = MiraAssets.RoundedBox.LoadAsset();
|
||||
buttonImg.type = Image.Type.Sliced;
|
||||
|
||||
var buttonText = CreateText(buttonObj, submitText, 20, Color.white);
|
||||
buttonText.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var colors = button.colors;
|
||||
colors.highlightedColor = Color.green;
|
||||
colors.pressedColor = MiraAssets.AcceptedTeal;
|
||||
button.colors = colors;
|
||||
|
||||
button.onClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
onSubmit(inputField.text);
|
||||
DestroyImmediate(canvasObj);
|
||||
}));
|
||||
|
||||
inputField.ActivateInputField();
|
||||
}
|
||||
|
||||
private static Text CreateText(GameObject parent, string textContent = "", int fontSize = 20, Color? textColor = null, Font? font = null)
|
||||
{
|
||||
var textObj = new GameObject("Text");
|
||||
textObj.transform.SetParent(parent.transform);
|
||||
|
||||
var text = textObj.AddComponent<Text>();
|
||||
text.font = font ?? Resources.GetBuiltinResource<Font>("Arial.ttf");
|
||||
text.text = textContent;
|
||||
text.color = textColor ?? Color.white;
|
||||
text.fontSize = fontSize;
|
||||
text.alignment = TextAnchor.MiddleCenter;
|
||||
|
||||
var rect = textObj.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(200, 40);
|
||||
rect.anchoredPosition = Vector2.zero;
|
||||
return text;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,18 @@ namespace MiraAPI.Modifiers;
|
||||
/// </summary>
|
||||
public static class ModifierManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether modifiers should be assigned to players.
|
||||
/// </summary>
|
||||
public static bool MiraAssignsModifiers { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of all registered modifiers.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<BaseModifier> Modifiers { get; internal set; } = [];
|
||||
|
||||
internal static List<BaseModifier> InternalModifiers { get; } = [];
|
||||
|
||||
private static readonly Dictionary<uint, Type> IdToTypeModifierMap = [];
|
||||
private static readonly Dictionary<Type, uint> TypeToIdModifierMap = [];
|
||||
private static readonly Dictionary<int, List<uint>> PrioritiesToIdsMap = [];
|
||||
@@ -75,6 +87,7 @@ public static class ModifierManager
|
||||
}
|
||||
|
||||
info.InternalModifiers.Add(modifier);
|
||||
InternalModifiers.Add(modifier);
|
||||
|
||||
if (modifier is not GameModifier gameModifier)
|
||||
{
|
||||
@@ -98,7 +111,11 @@ public static class ModifierManager
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void AssignModifiers(List<PlayerControl> plrs)
|
||||
/// <summary>
|
||||
/// Assigns modifiers to players. ONLY CALL THIS METHOD IF YOU KNOW WHAT YOU ARE DOING.
|
||||
/// </summary>
|
||||
/// <param name="plrs">The players to assign modifiers to.</param>
|
||||
public static void AssignModifiers(List<PlayerControl> plrs)
|
||||
{
|
||||
var rand = new Random();
|
||||
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// Allows players to paste text into chat.
|
||||
/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(ChatController), nameof(ChatController.Update))]
|
||||
public static class ChatControllerPatch
|
||||
{
|
||||
public static void Prefix(ChatController __instance)
|
||||
{
|
||||
if (!HudManager.Instance.Chat.IsOpenOrOpening) return;
|
||||
|
||||
var ctrlPressed = Input.GetKey(KeyCode.LeftControl) || Input.GetKey(KeyCode.RightControl);
|
||||
|
||||
if (!ctrlPressed || !Input.GetKeyDown(KeyCode.V)) return;
|
||||
|
||||
var clipboard = GUIUtility.systemCopyBuffer;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(clipboard)) return;
|
||||
clipboard = clipboard.Replace("<", string.Empty)
|
||||
.Replace(">", string.Empty)
|
||||
.Replace("\r", string.Empty);
|
||||
|
||||
if (!Input.GetKey(KeyCode.LeftShift) && !Input.GetKey(KeyCode.RightShift))
|
||||
clipboard = clipboard.Replace("\n", string.Empty);
|
||||
|
||||
__instance.freeChatField.textArea.SetText(__instance.freeChatField.textArea.text + clipboard);
|
||||
}
|
||||
}
|
||||
@@ -1,37 +1,25 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Hud;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Hud;
|
||||
|
||||
/// <summary>
|
||||
/// Reset button patches.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))]
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
|
||||
public static class ButtonResetPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Resets the cooldown and effect of all custom buttons when the meeting starts.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(MeetingHud), nameof(MeetingHud.Start))]
|
||||
public static void MeetingHudStartPostfix()
|
||||
public static void Postfix()
|
||||
{
|
||||
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
customActionButton.ResetCooldownAndOrEffect();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the cooldown and effect of all custom buttons after the exile screen is closed.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
|
||||
public static void ExileControllerReEnableGameplayPostfix()
|
||||
{
|
||||
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
customActionButton.ResetCooldownAndOrEffect();
|
||||
try
|
||||
{
|
||||
customActionButton.ResetCooldownAndOrEffect();
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Hud;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
@@ -77,7 +78,14 @@ public static class HudManagerPatches
|
||||
continue;
|
||||
}
|
||||
|
||||
button.CreateButton(location);
|
||||
try
|
||||
{
|
||||
button.CreateButton(location);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create custom button {button.GetType().Name}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
gridArrange.Start();
|
||||
@@ -105,7 +113,14 @@ public static class HudManagerPatches
|
||||
|
||||
foreach (var button in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
button.SetActive(isActive, role);
|
||||
try
|
||||
{
|
||||
button.SetActive(isActive, role);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to set custom button {button.GetType().Name} active: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ public static class HatsTabPatches
|
||||
yield return null;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__instance.SetScrollerBounds();
|
||||
yield return new WaitForSeconds(0.01f);
|
||||
}
|
||||
__instance.currentHatIsEquipped = true;
|
||||
|
||||
@@ -91,7 +91,7 @@ public static class NameplatesTabPatches
|
||||
hatIndex += 1;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__instance.SetScrollerBounds();
|
||||
__instance.currentNameplateIsEquipped = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ public static class SkinsTabPatches
|
||||
hatIndex += 1;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__instance.SetScrollerBounds();
|
||||
__instance.currentSkinIsEquipped = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ public static class VisorsTabPatches
|
||||
hatIndex += 1;
|
||||
}
|
||||
|
||||
__instance.scroller.ContentYBounds.max = -(__instance.YStart - (hatIndex + 1) / __instance.NumPerRow * __instance.YOffset) - 3f;
|
||||
__instance.SetScrollerBounds();
|
||||
__instance.currentVisorIsEquipped = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,7 @@ internal static class GameOptionsMenuPatch
|
||||
menu.checkboxOrigin,
|
||||
menu.numberOptionOrigin,
|
||||
menu.stringOptionOrigin,
|
||||
menu.playerOptionOrigin,
|
||||
menu.settingsContainer));
|
||||
|
||||
foreach (var newOpt in options)
|
||||
|
||||
@@ -89,7 +89,7 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
var prefab = GameSettingMenu.Instance.GameSettingsButton;
|
||||
|
||||
foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.Presets))
|
||||
foreach (var preset in MiraPluginManager.Instance.RegisteredPlugins.SelectMany(x => x.InternalPresets))
|
||||
{
|
||||
var button = Object.Instantiate(prefab, _presetHolder.transform);
|
||||
button.buttonText.text = preset.Name;
|
||||
@@ -112,7 +112,7 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
foreach (var button in mod.Presets.Select(x => x.PresetButton))
|
||||
foreach (var button in mod.InternalPresets.Select(x => x.PresetButton))
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
@@ -154,7 +154,7 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
foreach (var mod in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
foreach (var button in mod.Presets.Select(x => x.PresetButton))
|
||||
foreach (var button in mod.InternalPresets.Select(x => x.PresetButton))
|
||||
{
|
||||
if (button != null)
|
||||
{
|
||||
|
||||
@@ -288,7 +288,7 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
if (_text is not null && SelectedModIdx == 0)
|
||||
{
|
||||
_text.text = "Default";
|
||||
_text.text = $"<size=40%>(Page 0/{MiraPluginManager.Instance.RegisteredPlugins.Length})</size>\nDefault";
|
||||
_text.fontSizeMax = 3.2f;
|
||||
SelectedMod = null;
|
||||
}
|
||||
@@ -298,7 +298,7 @@ internal static class GameSettingMenuPatches
|
||||
SelectedMod = MiraPluginManager.Instance.RegisteredPlugins[SelectedModIdx - 1];
|
||||
|
||||
var name = SelectedMod.MiraPlugin.OptionsTitleText;
|
||||
_text.text = name[..Math.Min(name.Length, 25)];
|
||||
_text.text = $"<size=50%>(Page {SelectedModIdx}/{MiraPluginManager.Instance.RegisteredPlugins.Length})</size>\n" + name[..Math.Min(name.Length, 25)];
|
||||
}
|
||||
|
||||
bool replaceWithModifiers = true;
|
||||
@@ -495,7 +495,6 @@ internal static class GameSettingMenuPatches
|
||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||
|
||||
gameOptMenu.Initialize();
|
||||
|
||||
var positions = modifiers ? ModifiersPositions : OptionsPositions;
|
||||
if (positions.TryGetValue(SelectedModIdx, out var pos))
|
||||
{
|
||||
|
||||
@@ -129,8 +129,10 @@ public static class LobbyViewPanePatches
|
||||
__instance.rolesTabButton.SelectButton(false);
|
||||
|
||||
ModifiersTabButton?.SelectButton(true);
|
||||
|
||||
var filteredGroups = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier))==true));
|
||||
.Where(x => x.GroupVisible() && (x.ShowInModifiersMenu || (x.OptionableType != null && x.OptionableType.IsAssignableTo(typeof(BaseModifier)))));
|
||||
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
}
|
||||
|
||||
@@ -149,7 +151,8 @@ public static class LobbyViewPanePatches
|
||||
}
|
||||
|
||||
var filteredGroups = SelectedMod.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && x.GroupVisible.Invoke());
|
||||
.Where(x => x is { ShowInModifiersMenu: false, OptionableType: null } && x.GroupVisible());
|
||||
|
||||
DrawOptions(__instance, filteredGroups);
|
||||
return false;
|
||||
}
|
||||
@@ -176,12 +179,15 @@ public static class LobbyViewPanePatches
|
||||
{
|
||||
var num = 1.44f;
|
||||
|
||||
foreach (var group in groups)
|
||||
var groupArray = groups.Where(x => x.GroupVisible() && x.Options.Any(y => y.Visible())).ToArray();
|
||||
|
||||
foreach (var group in groupArray)
|
||||
{
|
||||
var categoryHeaderMasked = Object.Instantiate(
|
||||
menu.categoryHeaderOrigin,
|
||||
menu.settingsContainer,
|
||||
true);
|
||||
|
||||
categoryHeaderMasked.SetHeader(StringNames.Name, 61);
|
||||
categoryHeaderMasked.Title.text = group.GroupName;
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one;
|
||||
@@ -193,7 +199,7 @@ public static class LobbyViewPanePatches
|
||||
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
if (!option.Visible.Invoke())
|
||||
if (!option.Visible())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -202,6 +208,7 @@ public static class LobbyViewPanePatches
|
||||
menu.infoPanelOrigin,
|
||||
menu.settingsContainer,
|
||||
true);
|
||||
|
||||
viewSettingsInfoPanel.transform.localScale = Vector3.one;
|
||||
float num2;
|
||||
if (i % 2 == 0)
|
||||
@@ -240,7 +247,7 @@ public static class LobbyViewPanePatches
|
||||
num -= 0.85f;
|
||||
}
|
||||
|
||||
menu.scrollBar.CalculateAndSetYBounds(menu.settingsInfo.Count + 10, 2f, 6f, 0.85f);
|
||||
menu.scrollBar.SetYBoundsMax(-num - 2);
|
||||
}
|
||||
|
||||
private static void DrawRolesTab(LobbyViewSettingsPane instance)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
using Object = Il2CppSystem.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
@@ -1,12 +1,161 @@
|
||||
using HarmonyLib;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
using Object = Il2CppSystem.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class OptionsPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseChance))]
|
||||
public static bool RoleIncreaseChancePrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleChance == 0)
|
||||
{
|
||||
__instance.roleMaxCount = 1;
|
||||
}
|
||||
|
||||
var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10;
|
||||
__instance.roleChance += increment;
|
||||
|
||||
if (__instance.roleChance > 100)
|
||||
{
|
||||
__instance.roleChance = 0;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseChance))]
|
||||
public static bool RoleDecreaseChancePrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleChance == 0)
|
||||
{
|
||||
__instance.roleMaxCount = 1;
|
||||
}
|
||||
|
||||
var increment = Input.GetKey(KeyCode.LeftShift) ? 5 : 10;
|
||||
__instance.roleChance -= increment;
|
||||
|
||||
if (__instance.roleChance < 0)
|
||||
{
|
||||
__instance.roleChance = 100;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.IncreaseCount))]
|
||||
public static bool RoleIncreaseCountPrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleMaxCount == 0)
|
||||
{
|
||||
__instance.roleChance = 50;
|
||||
}
|
||||
|
||||
__instance.roleMaxCount += 1;
|
||||
if (__instance.roleMaxCount > __instance.role.MaxCount)
|
||||
{
|
||||
__instance.roleMaxCount = 0;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.DecreaseCount))]
|
||||
public static bool RoleDecreaseCountPrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (!__instance.Role.IsCustomRole())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.roleMaxCount == 0)
|
||||
{
|
||||
__instance.roleChance = 50;
|
||||
}
|
||||
|
||||
__instance.roleMaxCount -= 1;
|
||||
if (__instance.roleMaxCount < 0)
|
||||
{
|
||||
__instance.roleMaxCount = __instance.role.MaxCount;
|
||||
}
|
||||
|
||||
__instance.OnValueChanged.Invoke(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.AdjustButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustChanceButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(RoleOptionSetting), nameof(RoleOptionSetting.AdjustCountButtonsActiveState))]
|
||||
public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
|
||||
{
|
||||
if (__instance.IsCustom())
|
||||
{
|
||||
if (__instance.TryCast<NumberOption>() is { } numberOption)
|
||||
{
|
||||
numberOption.MinusBtn.SetInteractable(true);
|
||||
numberOption.PlusBtn.SetInteractable(true);
|
||||
}
|
||||
if (__instance.TryCast<StringOption>() is { } stringOption)
|
||||
{
|
||||
stringOption.MinusBtn.SetInteractable(true);
|
||||
stringOption.PlusBtn.SetInteractable(true);
|
||||
}
|
||||
if (__instance.TryCast<PlayerOption>() is { } playerOption)
|
||||
{
|
||||
playerOption.MinusBtn.SetInteractable(true);
|
||||
playerOption.PlusBtn.SetInteractable(true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (__instance.TryCast<RoleOptionSetting>() is { } roleOptionSetting && roleOptionSetting.Role.IsCustomRole())
|
||||
{
|
||||
roleOptionSetting.CountMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.CountPlusBtn.SetInteractable(true);
|
||||
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(ToggleOption), nameof(ToggleOption.Initialize))]
|
||||
public static bool ToggleInit(ToggleOption __instance)
|
||||
@@ -16,8 +165,7 @@ public static class OptionsPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.TitleText.text =
|
||||
TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
__instance.TitleText.text = TranslationController.Instance.GetString(__instance.Title, Array.Empty<Object>());
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -40,19 +188,12 @@ public static class OptionsPatches
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.UpdateValue))]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.UpdateValue))]
|
||||
[HarmonyPatch(typeof(PlayerOption), nameof(PlayerOption.UpdateValue))]
|
||||
public static bool UpdateValuePrefix(OptionBehaviour __instance)
|
||||
{
|
||||
return !__instance.IsCustom();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.AdjustButtonsActiveState))]
|
||||
[HarmonyPatch(typeof(StringOption), nameof(StringOption.AdjustButtonsActiveState))]
|
||||
public static bool AdjustButtonsPrefix(OptionBehaviour __instance)
|
||||
{
|
||||
return !__instance.IsCustom();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(NumberOption), nameof(NumberOption.Initialize))]
|
||||
public static bool NumberInit(NumberOption __instance)
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
using Object = Il2CppSystem.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
[HarmonyPatch(typeof(PlayerOption))]
|
||||
public static class PlayerOptionPatches
|
||||
{
|
||||
// Updates the list of players according to the filter.
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerOption.FixedUpdate))]
|
||||
public static bool PlayerOptionUpdatePatch(PlayerOption __instance)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!ModdedOptionsManager.CreatedPlayerOptions.TryGetValue(__instance, out var moddedPlrOpt)) return false;
|
||||
|
||||
var filteredPlayers = moddedPlrOpt.GetFilteredPlayers();
|
||||
if (filteredPlayers.Count != __instance.Values.Count)
|
||||
{
|
||||
filteredPlayers.Sort((a, b) => a.PlayerId.CompareTo(b.PlayerId));
|
||||
|
||||
__instance.Values = filteredPlayers.ToIl2CppList();
|
||||
moddedPlrOpt.Values = __instance.Values;
|
||||
__instance.playerIndex = filteredPlayers.FindIndex(p => p.PlayerId == __instance.Value);
|
||||
__instance.SetValueText();
|
||||
}
|
||||
|
||||
__instance.CheckValueChanged();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Disable going under 0 if you aren't allowed to choose None.
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerOption.UpdatePlayerIndex))]
|
||||
public static bool UpdatePlayerIndexPatch(PlayerOption __instance, int index)
|
||||
{
|
||||
if (!__instance.IsCustom() || ModdedOptionsManager.CreatedPlayerOptions[__instance].AllowNone)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var newIndex = Mathf.Clamp(index, 0, __instance.Values.Count - 1);
|
||||
__instance.playerIndex = newIndex;
|
||||
__instance.Value = __instance.Values[newIndex].PlayerId;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Replace "Round Robin" with "None" in the Host Options Menu.
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(PlayerOption.SetValueText))]
|
||||
public static void SetValueTextPatch(PlayerOption __instance)
|
||||
{
|
||||
if (__instance.playerIndex < 0)
|
||||
{
|
||||
__instance.ValueText.text = TranslationController.Instance.GetString(StringNames.FilterNone);
|
||||
}
|
||||
}
|
||||
|
||||
// Innersloth hardcode literally replaces the value with the HNS impostor option OnEnable, so I have to patch that out. Smh.
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerOption.OnEnable))]
|
||||
public static bool OnEnablePatch(PlayerOption __instance)
|
||||
{
|
||||
return !__instance.IsCustom();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerOption.GetInt))]
|
||||
public static bool GetIntPatch(PlayerOption __instance, ref int __result)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__result = __instance.Value;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Used to replace "Round Robin" with "None" for option notification & view pane.
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PlayerSelectionGameSetting), nameof(PlayerSelectionGameSetting.GetValueString))]
|
||||
public static bool GetStringPatch(PlayerSelectionGameSetting __instance, float value, ref string __result)
|
||||
{
|
||||
var option = ModdedOptionsManager.ModdedOptions.FirstOrDefault(x => x.Value.Data == __instance).Value;
|
||||
if (option == null) return true;
|
||||
|
||||
var networkedPlayerInfo = GameData.Instance.AllPlayers.ToArray().FirstOrDefault(p => p.PlayerId == (int)value);
|
||||
if (networkedPlayerInfo != null)
|
||||
{
|
||||
__result = networkedPlayerInfo.PlayerName;
|
||||
return false;
|
||||
}
|
||||
|
||||
__result = TranslationController.Instance.GetString(StringNames.FilterNone);
|
||||
return false;
|
||||
}
|
||||
|
||||
// "UpdateValue" was inlined and I needed to replace it, so I just rewrote the method where it's called from.
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerOption.CheckValueChanged))]
|
||||
public static bool UpdateValuePatch(PlayerOption __instance)
|
||||
{
|
||||
if (!__instance.IsCustom())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (__instance.oldValue == __instance.Value) return false;
|
||||
|
||||
__instance.oldValue = __instance.Value;
|
||||
__instance.SetValueText();
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -30,6 +31,9 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
private static float ScrollerNum { get; set; } = 0.522f;
|
||||
|
||||
private static RoleBehaviour? CurrentRole { get; set; }
|
||||
private static List<IModdedOption>? CurrentRoleOptions { get; set; }
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.SetQuotaTab))]
|
||||
public static void SetQuotaTabPostfix(RolesSettingsMenu __instance)
|
||||
@@ -45,6 +49,8 @@ public static class RoleSettingMenuPatches
|
||||
RoleOptionSettings.ForEach(Object.Destroy);
|
||||
Headers.Clear();
|
||||
RoleOptionSettings.Clear();
|
||||
CurrentRole = null;
|
||||
CurrentRoleOptions = null;
|
||||
|
||||
__instance.roleChances = new Il2CppSystem.Collections.Generic.List<RoleOptionSetting>();
|
||||
__instance.advancedSettingChildren = new Il2CppSystem.Collections.Generic.List<OptionBehaviour>();
|
||||
@@ -211,9 +217,10 @@ public static class RoleSettingMenuPatches
|
||||
headerBtn.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
if (RoleGroupHidden.TryGetValue(group, out var value))
|
||||
RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
|
||||
if (RoleGroupHidden.TryGetValue(group, out var groupHidden))
|
||||
{
|
||||
RoleGroupHidden[group] = !value;
|
||||
RoleGroupHidden[group] = !groupHidden;
|
||||
}
|
||||
foreach (var header in Headers)
|
||||
{
|
||||
@@ -234,6 +241,7 @@ public static class RoleSettingMenuPatches
|
||||
ScrollerNum -= 0.4f;
|
||||
}
|
||||
}
|
||||
|
||||
__instance.scrollBar.SetScrollBounds(__instance);
|
||||
return false;
|
||||
}
|
||||
@@ -246,7 +254,7 @@ public static class RoleSettingMenuPatches
|
||||
return;
|
||||
}
|
||||
|
||||
scroller.CalculateAndSetYBounds(1.5f * Headers.Count + RoleOptionSettings.Count, 1f, 6f, 0.43f);
|
||||
scroller.SetYBoundsMax(-ScrollerNum - 2);
|
||||
if (RolePositions.TryGetValue(GameSettingMenuPatches.SelectedModIdx, out var scroll))
|
||||
{
|
||||
scroller.Inner.localPosition = scroll;
|
||||
@@ -260,9 +268,31 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RolesSettingsMenu.Update))]
|
||||
public static bool UpdatePatch()
|
||||
public static bool UpdatePatch(RolesSettingsMenu __instance)
|
||||
{
|
||||
return GameSettingMenuPatches.SelectedModIdx == 0;
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0) return true;
|
||||
if (CurrentRole == null || CurrentRoleOptions == null) return false;
|
||||
|
||||
var hasImage = CurrentRole.RoleScreenshot != null;
|
||||
var num = hasImage ? -0.872f : -1;
|
||||
|
||||
foreach (var opt in CurrentRoleOptions)
|
||||
{
|
||||
if (opt.OptionBehaviour == null) continue;
|
||||
|
||||
if (!opt.Visible.Invoke())
|
||||
{
|
||||
opt.OptionBehaviour.gameObject.SetActive(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
opt.OptionBehaviour.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
|
||||
opt.OptionBehaviour.gameObject.SetActive(true);
|
||||
num += -0.45f;
|
||||
}
|
||||
|
||||
__instance.scrollBar.SetYBoundsMax(-num - 3);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
@@ -274,6 +304,8 @@ public static class RoleSettingMenuPatches
|
||||
return;
|
||||
}
|
||||
|
||||
CurrentRole = null;
|
||||
CurrentRoleOptions = null;
|
||||
__instance.scrollBar.SetScrollBounds(__instance);
|
||||
}
|
||||
|
||||
@@ -326,26 +358,26 @@ public static class RoleSettingMenuPatches
|
||||
optBehaviour.gameObject.DestroyImmediate();
|
||||
}
|
||||
|
||||
CurrentRole = role;
|
||||
__instance.advancedSettingChildren.Clear();
|
||||
|
||||
var hasImage = role.RoleScreenshot != null;
|
||||
var num = hasImage ? -0.872f : -1;
|
||||
|
||||
// TODO: create sub groups under the role settings.
|
||||
var filteredOptions = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x=> x.OptionableType == role.GetType())
|
||||
.SelectMany(x=>x.Options)
|
||||
.Where(x => x.GroupVisible() && x.OptionableType == role.GetType())
|
||||
.SelectMany(x => x.Options)
|
||||
.ToList() ?? [];
|
||||
|
||||
CurrentRoleOptions = filteredOptions;
|
||||
|
||||
foreach (var option in filteredOptions)
|
||||
{
|
||||
var newOpt = option.CreateOption(
|
||||
__instance.checkboxOrigin,
|
||||
__instance.numberOptionOrigin,
|
||||
__instance.stringOptionOrigin,
|
||||
GameSettingMenu.Instance.GameSettingsTab.playerOptionOrigin,
|
||||
__instance.AdvancedRolesSettings.transform);
|
||||
|
||||
newOpt.transform.localPosition = new Vector3(hasImage ? 2.17f : 1.1f, num, -2f);
|
||||
newOpt.SetClickMask(__instance.ButtonClickMask);
|
||||
|
||||
SpriteRenderer[] componentsInChildren = newOpt.GetComponentsInChildren<SpriteRenderer>(true);
|
||||
@@ -363,11 +395,10 @@ public static class RoleSettingMenuPatches
|
||||
newOpt.LabelBackground.enabled = false;
|
||||
__instance.advancedSettingChildren.Add(newOpt);
|
||||
|
||||
num += -0.45f;
|
||||
newOpt.gameObject.SetActive(false);
|
||||
newOpt.Initialize();
|
||||
}
|
||||
|
||||
__instance.scrollBar.CalculateAndSetYBounds(__instance.advancedSettingChildren.Count + 3, 1f, 6f, 0.45f);
|
||||
__instance.scrollBar.ScrollToTop();
|
||||
}
|
||||
|
||||
@@ -491,6 +522,10 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor;
|
||||
roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged);
|
||||
roleOptionSetting.SetClickMask(__instance.ButtonClickMask);
|
||||
roleOptionSetting.ChanceMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.ChancePlusBtn.SetInteractable(true);
|
||||
roleOptionSetting.CountMinusBtn.SetInteractable(true);
|
||||
roleOptionSetting.CountPlusBtn.SetInteractable(true);
|
||||
__instance.roleChances.Add(roleOptionSetting);
|
||||
|
||||
roleOptionSetting.titleText.transform.localPosition = new Vector3(-0.5376f, -0.2923f, 0f);
|
||||
@@ -499,7 +534,7 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
if (GameSettingMenuPatches.SelectedMod is null ||
|
||||
GameSettingMenuPatches.SelectedMod.InternalOptionGroups
|
||||
.Exists(x => x.OptionableType == role.GetType()))
|
||||
.Exists(x => x.GroupVisible() && x.OptionableType == role.GetType() && x.Options.Any(y => y.Visible())))
|
||||
{
|
||||
var newButton = Object.Instantiate(roleOptionSetting.buttons[0], roleOptionSetting.transform);
|
||||
newButton.name = "ConfigButton";
|
||||
|
||||
@@ -7,6 +7,7 @@ using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Voting;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
@@ -94,12 +95,19 @@ internal static class PlayerControlPatches
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!button.Enabled(__instance.Data?.Role))
|
||||
try
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!button.Enabled(__instance.Data?.Role))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
button.FixedUpdateHandler(__instance);
|
||||
button.FixedUpdateHandler(__instance);
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to update custom button {button.GetType().Name}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,11 @@ public static class GameStartupPatch
|
||||
|
||||
_runOnce = true;
|
||||
|
||||
if (MiraPluginManager.Instance.QueuedRoleRegistrations.Count <= 0) return;
|
||||
if (MiraPluginManager.Instance.QueuedRoleRegistrations.Count <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var queue in MiraPluginManager.Instance.QueuedRoleRegistrations)
|
||||
{
|
||||
CustomRoleManager.RegisterRoleTypes(queue.Value, queue.Key);
|
||||
|
||||
@@ -65,7 +65,7 @@ public static class RoleManagerPatches
|
||||
[HarmonyPatch(nameof(RoleManager.SelectRoles))]
|
||||
public static void ModifierSelectionPatches(RoleManager __instance)
|
||||
{
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
if (!AmongUsClient.Instance.AmHost || !ModifierManager.MiraAssignsModifiers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))]
|
||||
public static class ServerDropdownPatch
|
||||
{
|
||||
public static bool Prefix(ServerDropdown __instance)
|
||||
{
|
||||
var num = 0;
|
||||
__instance.background.size = new Vector2(8.4f, 4.8f);
|
||||
|
||||
foreach (var regionInfo in DestroyableSingleton<ServerManager>.Instance.AvailableRegions)
|
||||
{
|
||||
if (DestroyableSingleton<ServerManager>.Instance.CurrentRegion.Equals(regionInfo))
|
||||
{
|
||||
__instance.defaultButtonSelected = __instance.firstOption;
|
||||
__instance.firstOption.ChangeButtonText(
|
||||
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
|
||||
regionInfo.TranslateName,
|
||||
regionInfo.Name));
|
||||
}
|
||||
else
|
||||
{
|
||||
var region = regionInfo;
|
||||
var serverListButton = __instance.ButtonPool.Get<ServerListButton>();
|
||||
var x = num % 2 == 0 ? -2 : 2;
|
||||
var y = -0.55f * (num / 2);
|
||||
serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f);
|
||||
serverListButton.transform.localScale = Vector3.one;
|
||||
serverListButton.Text.text =
|
||||
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
|
||||
regionInfo.TranslateName,
|
||||
regionInfo.Name);
|
||||
serverListButton.Text.ForceMeshUpdate();
|
||||
serverListButton.Button.OnClick.RemoveAllListeners();
|
||||
serverListButton.Button.OnClick.AddListener((UnityAction)(() => { __instance.ChooseOption(region); }));
|
||||
__instance.controllerSelectable.Add(serverListButton.Button);
|
||||
__instance.background.transform.localPosition = new Vector3(
|
||||
0f,
|
||||
__instance.initialYPos + (-0.3f * (num / 2)),
|
||||
0f);
|
||||
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2)));
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
public interface IMiraExtension
|
||||
{
|
||||
public Type GetBasePluginType();
|
||||
}
|
||||
@@ -37,99 +37,31 @@ public sealed class MiraPluginManager
|
||||
Instance = this;
|
||||
IL2CPPChainloader.Instance.PluginLoad += (pluginInfo, assembly, plugin) =>
|
||||
{
|
||||
if (plugin is not IMiraPlugin miraPlugin)
|
||||
if (plugin is IMiraPlugin miraPlugin)
|
||||
{
|
||||
return;
|
||||
var miraInfo = new MiraPluginInfo(miraPlugin, pluginInfo);
|
||||
RegisterPlugin(miraInfo, assembly);
|
||||
_registeredPlugins.Add(assembly, miraInfo);
|
||||
}
|
||||
|
||||
var info = new MiraPluginInfo(miraPlugin, pluginInfo);
|
||||
var roles = new List<Type>();
|
||||
|
||||
var oldConfigSetting = info.PluginConfig.SaveOnConfigSet;
|
||||
info.PluginConfig.SaveOnConfigSet = false;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
else if (plugin is IMiraExtension miraExtension)
|
||||
{
|
||||
if (type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
|
||||
var basePlugin = miraExtension.GetBasePluginType();
|
||||
var basePluginInfo = _registeredPlugins.Values.FirstOrDefault(x => x.MiraPlugin.GetType() == basePlugin);
|
||||
if (basePluginInfo == null)
|
||||
{
|
||||
continue;
|
||||
Logger<MiraApiPlugin>.Error($"Base plugin {basePlugin.Name} not found for extension {pluginInfo.Metadata.GUID}.");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var method in type.GetMethods())
|
||||
{
|
||||
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
|
||||
if (eventAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramType = parameters[0].ParameterType;
|
||||
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
|
||||
}
|
||||
|
||||
if (RegisterModifier(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterOptions(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterRole(type, info, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterButton(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameOver(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterColorClasses(type);
|
||||
RegisterPlugin(basePluginInfo, assembly);
|
||||
}
|
||||
|
||||
info.PluginConfig.Save();
|
||||
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||
|
||||
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
QueuedRoleRegistrations.Add(info, roles);
|
||||
|
||||
_registeredPlugins.Add(assembly, info);
|
||||
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
|
||||
};
|
||||
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
|
||||
IL2CPPChainloader.Instance.Finished += () =>
|
||||
{
|
||||
// Save all buttons into a read-only collection for easy access
|
||||
CustomButtonManager.Buttons = new ReadOnlyCollection<CustomActionButton>(CustomButtonManager.CustomButtons);
|
||||
|
||||
// Cache all the registered plugins into an array for easy access
|
||||
RegisteredPlugins = [.._registeredPlugins.Values];
|
||||
RegisteredPlugins = [.. _registeredPlugins.Values];
|
||||
ModifierManager.Modifiers = new ReadOnlyCollection<BaseModifier>(ModifierManager.InternalModifiers);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -143,6 +75,90 @@ public sealed class MiraPluginManager
|
||||
return Instance._registeredPlugins.Values.FirstOrDefault(plugin => plugin.PluginId == pluginId);
|
||||
}
|
||||
|
||||
private void RegisterPlugin(MiraPluginInfo info, Assembly assembly)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"Registering plugin {info.PluginId} with Mira API.");
|
||||
|
||||
var roles = new List<Type>();
|
||||
|
||||
var oldConfigSetting = info.PluginConfig.SaveOnConfigSet;
|
||||
info.PluginConfig.SaveOnConfigSet = false;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
if (type.GetCustomAttribute<MiraIgnoreAttribute>() != null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (var method in type.GetMethods())
|
||||
{
|
||||
var eventAttribute = method.GetCustomAttribute<RegisterEventAttribute>();
|
||||
if (eventAttribute == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var paramType = parameters[0].ParameterType;
|
||||
MiraEventManager.RegisterEventHandler(paramType, method, eventAttribute.Priority);
|
||||
}
|
||||
|
||||
if (RegisterModifier(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterOptions(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterRole(type, info, out var role))
|
||||
{
|
||||
roles.Add(role);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterButton(type, info))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (RegisterGameOver(type))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
RegisterColorClasses(type);
|
||||
}
|
||||
|
||||
info.PluginConfig.Save();
|
||||
info.PluginConfig.SaveOnConfigSet = oldConfigSetting;
|
||||
|
||||
info.InternalOptionGroups.Sort((x, y) => x.GroupPriority.CompareTo(y.GroupPriority));
|
||||
|
||||
QueuedRoleRegistrations.TryAdd(info, []);
|
||||
QueuedRoleRegistrations[info].AddRange(roles);
|
||||
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
Logger<MiraApiPlugin>.Info($"{info.PluginId} finished registering.");
|
||||
}
|
||||
|
||||
private static bool RegisterGameOver(Type type)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -69,7 +69,8 @@ public static class PresetManager
|
||||
|
||||
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"Loading preset file {file}");
|
||||
var fileName = Path.GetFileName(file);
|
||||
Logger<MiraApiPlugin>.Info($"Loading preset file {fileName}");
|
||||
var presetName = Path.GetFileNameWithoutExtension(file);
|
||||
var presetConfig = new ConfigFile(file, false)
|
||||
{
|
||||
@@ -90,6 +91,6 @@ public static class PresetManager
|
||||
|
||||
plugin.InternalPresets.Add(new OptionPreset(presetName, plugin, presetConfig));
|
||||
}
|
||||
plugin.Presets = [.. plugin.InternalPresets];
|
||||
plugin.Presets = [..plugin.InternalPresets];
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Fields;
|
||||
using MiraAPI.Modifiers.ModifierDisplay;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Attributes;
|
||||
@@ -9,16 +7,21 @@ using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
|
||||
|
||||
[RegisterInIl2Cpp]
|
||||
public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
#pragma warning disable S3903
|
||||
#pragma warning disable CA1050
|
||||
public class SavePresetPopup(nint cppPtr) : Minigame(cppPtr)
|
||||
#pragma warning restore CA1050
|
||||
#pragma warning restore S3903
|
||||
{
|
||||
private PassiveButton saveButton;
|
||||
private PassiveButton closeButton;
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
private TextMeshPro textBoxText;
|
||||
private TextBoxTMP textBox;
|
||||
|
||||
private Action<string> onSave;
|
||||
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
// Cleanup holder object
|
||||
public void OnDestroy()
|
||||
@@ -28,13 +31,14 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
|
||||
public override void Close()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
var textboxHolder = transform.GetChild(1).GetChild(1);
|
||||
saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
|
||||
closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
|
||||
var saveButton = transform.FindChild("SaveButton").GetComponent<PassiveButton>();
|
||||
var closeButton = transform.FindChild("CloseButton").GetComponent<PassiveButton>();
|
||||
textBox = textboxHolder.GetChild(0).GetComponent<TextBoxTMP>();
|
||||
textBoxText = textBox.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
|
||||
@@ -64,7 +68,11 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
|
||||
foreach (var tmp in gameObject.GetComponentsInChildren<TextMeshPro>())
|
||||
{
|
||||
if (tmp.gameObject.transform.parent.name == "Textbox") continue;
|
||||
if (tmp.gameObject.transform.parent.name == "Textbox")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var text = tmp.text;
|
||||
tmp.text = $"<font=\"LiberationSans SDF\" material=\"LiberationSans SDF RadialMenu Material\">{text}</font>";
|
||||
}
|
||||
@@ -78,9 +86,9 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
transform =
|
||||
{
|
||||
parent = Camera.main!.transform,
|
||||
localPosition = new Vector3(0, -1f, -500),
|
||||
},
|
||||
};
|
||||
holder.transform.localPosition = new Vector3(0, -1f, -500);
|
||||
|
||||
var gameObject = Instantiate(MiraAssets.PresetSavePopup.LoadAsset(), holder.transform);
|
||||
var popup = gameObject.GetComponent<SavePresetPopup>();
|
||||
@@ -88,4 +96,3 @@ public class SavePresetPopup(nint ptr) : Minigame(ptr)
|
||||
popup.Begin(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,6 +35,16 @@ public static class CustomRoleManager
|
||||
public static readonly LoadableAsset<AudioClip> ImpostorIntroSound =
|
||||
CustomRoleUtils.GetIntroSound(RoleTypes.Impostor)!;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of custom roles as RoleBehaviour objects.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<RoleBehaviour> CustomRoleBehaviours { get; private set; } = [];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of custom roles as ICustomRole objects.
|
||||
/// </summary>
|
||||
public static IReadOnlyList<ICustomRole> CustomMiraRoles { get; private set; } = [];
|
||||
|
||||
internal static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
|
||||
internal static readonly Dictionary<Type, ushort> RoleIds = [];
|
||||
|
||||
@@ -53,6 +63,9 @@ public static class CustomRoleManager
|
||||
{
|
||||
RoleManager.GhostRoles.Add(role.Role);
|
||||
}
|
||||
|
||||
CustomRoleBehaviours = CustomRoles.Values.ToList();
|
||||
CustomMiraRoles = CustomRoles.Values.OfType<ICustomRole>().ToList();
|
||||
}
|
||||
|
||||
internal static void RegisterRoleTypes(List<Type> roles, MiraPluginInfo pluginInfo)
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using AmongUs.GameOptions;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
@@ -56,7 +57,21 @@ public static class CustomRoleUtils
|
||||
public static LoadableAsset<AudioClip>? GetIntroSound(RoleTypes roleType)
|
||||
{
|
||||
var role = RoleManager.Instance.AllRoles.FirstOrDefault(role => role.Role == roleType);
|
||||
if (role is ICustomRole customRole) return customRole.Configuration.IntroSound;
|
||||
if (role is ICustomRole customRole)
|
||||
{
|
||||
return customRole.Configuration.IntroSound;
|
||||
}
|
||||
|
||||
return new PreloadedAsset<AudioClip>(role!.IntroSound);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a role is a custom role or not.
|
||||
/// </summary>
|
||||
/// <param name="role">The RoleBehaviour to check.</param>
|
||||
/// <returns>True if the role is a custom role, false otherwise.</returns>
|
||||
public static bool IsCustomRole(this RoleBehaviour role)
|
||||
{
|
||||
return CustomRoleManager.CustomRoles.ContainsKey((ushort)role.Role);
|
||||
}
|
||||
}
|
||||
@@ -49,4 +49,4 @@ public static class SpriteTools
|
||||
sprite.name = resourcePath;
|
||||
return sprite;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public static class Extensions
|
||||
analytics.MinigameClosed(data, taskType, realtimeSinceStartup, isComplete);
|
||||
self.StartCoroutine(self.CoDestroySelf());
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Sets the cooldown of a button with a formatted string.
|
||||
/// </summary>
|
||||
@@ -154,6 +154,24 @@ public static class Extensions
|
||||
return TutorialManager.InstanceExists || AmongUsClient.Instance.HostId == playerControl.OwnerId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Used to convert a System.Collections.Generic.List to Il2cppSystem.
|
||||
/// </summary>
|
||||
/// <param name="systemList">The list.</param>
|
||||
/// <typeparam name="T">The type in the list.</typeparam>
|
||||
/// <returns>The converted list.</returns>
|
||||
public static Il2CppSystem.Collections.Generic.List<T> ToIl2CppList<T>(this List<T> systemList)
|
||||
{
|
||||
var il2cppList = new Il2CppSystem.Collections.Generic.List<T>();
|
||||
|
||||
foreach (var item in systemList)
|
||||
{
|
||||
il2cppList.Add(item);
|
||||
}
|
||||
|
||||
return il2cppList;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if a float is an integer.
|
||||
/// </summary>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
[](https://discord.gg/AEfHJGwggC)
|
||||
|
||||
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
|
||||
|
||||
# Mira API
|
||||
|
||||
A thorough, but simple, Among Us modding API and utility library that covers:
|
||||
@@ -8,27 +10,32 @@ A thorough, but simple, Among Us modding API and utility library that covers:
|
||||
- Modifiers
|
||||
- Buttons
|
||||
- Custom Colors
|
||||
- Events
|
||||
- Voting
|
||||
- Assets
|
||||
- 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.
|
||||
Mira API strives to be comprehensive, yet straightforward, 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**
|
||||
**Join the [Discord](https://discord.gg/FYYqJU2bvp) 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](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.
|
||||
3. Implement the IMiraPlugin interface in your plugin class.
|
||||
|
||||
Mira API also depends on [Reactor](https://github.com/NuclearPowered/Reactor) in order to function properly! Do not forget to include it as a reference and `BepInDependency`!
|
||||
Mira API also depends on [Reactor](https://github.com/NuclearPowered/Reactor) to function properly!
|
||||
Remember to include it as a reference and `BepInDependency`!
|
||||
|
||||
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.
|
||||
It is highly recommended to follow this project structure when using Mira API to keep your code clean and organized.
|
||||
You can also view the Example Mod in this repository for some guidance.
|
||||
```
|
||||
MyMiraMod/
|
||||
├── Buttons/
|
||||
@@ -54,28 +61,36 @@ MyMiraMod/
|
||||
```
|
||||
|
||||
## 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 `CrewmateRole`, `ImpostorRole`, etc)
|
||||
Roles are straightforward in Mira API. There are two things you need to do to create a custom role:
|
||||
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!
|
||||
Note: For step 1, if you are making neutral roles,
|
||||
you can choose either `CrewmateRole` or `ImpostorRole`,
|
||||
or even `RoleBehaviour` if you are okay with starting from scratch.
|
||||
|
||||
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.
|
||||
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 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:
|
||||
Mira provides three 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
|
||||
- `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.
|
||||
Modifiers provide various overridable functions and properties for custom behavior.
|
||||
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.
|
||||
|
||||
@@ -83,9 +98,17 @@ An example Game modifier can be found [here](https://github.com/All-Of-Us-Mods/M
|
||||
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. 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.
|
||||
Options are also 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.
|
||||
To create a group, you need to create a class that inherits from the `AbstractOptionGroup` abstract class.
|
||||
Groups contain some properties for developers to control,
|
||||
including `GroupName`, `GroupColor`, `GroupVisible`, and `OptionableType`.
|
||||
The full list of properties can be found in the `AbstractOptionGroup` class.
|
||||
Only the `GroupName` is required.
|
||||
|
||||
Here is an example of a group class:
|
||||
```csharp
|
||||
@@ -141,46 +164,39 @@ And this is an example of a ModdedOption property:
|
||||
public ModdedToggleOption YeezusAbility { get; } = new ModdedToggleOption("Yeezus Ability", false);
|
||||
```
|
||||
|
||||
Here is a full list of ModdedOption classes you can use:
|
||||
There are currently three types of ModdedOptions you can create:
|
||||
- `ModdedEnumOption`
|
||||
- `ModdedNumberOption`
|
||||
- `ModdedToggleOption`
|
||||
|
||||
To see a full example of an options class, see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
|
||||
To see a full example of an option class,
|
||||
see [this file](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/Options/ExampleOptions.cs).
|
||||
|
||||
### Role Options
|
||||
|
||||
You can also specify a role type for an option or option group.
|
||||
|
||||
To set the role type for an entire group, set the `AdvancedRole` property on that group like this:
|
||||
To set the role type for an entire group, set the `OptionableType` property on that group like this:
|
||||
```csharp
|
||||
public class MyOptionsGroup : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName => "My Options";
|
||||
public override Type AdvancedRole => typeof(MyRole); // this is the role that will have these options
|
||||
public override Type? OptionableType => typeof(MyRole); // this is the role that will have these options
|
||||
|
||||
[ModdedNumberOption("Ability Uses", min: 0, max: 10)]
|
||||
public float AbilityUses { get; set; } = 5f;
|
||||
}
|
||||
```
|
||||
|
||||
To set the role type for individual options, specify the `roleType` parameter in the option like this:
|
||||
```csharp
|
||||
// this group doesnt specify a role, so it will show up in the global settings
|
||||
public class MyOptionsGroup : AbstractOptionGroup
|
||||
{
|
||||
public override string GroupName => "My Options";
|
||||
|
||||
// this option will only show up in the settings for MyRole
|
||||
[ModdedNumberOption("Ability Uses", min: 0, max: 10, roleType: typeof(MyRole))]
|
||||
public float AbilityUses { get; set; } = 5f;
|
||||
}
|
||||
```
|
||||
You can also use a modifier type for the `OptionableType` property.
|
||||
This will associate that option group with the specified modifier type
|
||||
and display it in the modifier options menu.
|
||||
|
||||
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.
|
||||
Mira API provides its 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
|
||||
@@ -190,18 +206,23 @@ This will kill a player without creating a dead body and without teleporting the
|
||||
|
||||
## 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.
|
||||
Mira API provides a simple interface for adding ability buttons to the game.
|
||||
|
||||
All other tasks and logic required to add the button to the game are handled by Mira API.
|
||||
The only thing you need to do is create a class
|
||||
that inherits from the `CustomActionButton` class and implement the properties and methods.
|
||||
|
||||
In case you need to access your `CustomActionButton` instance from another class, you can use the `CustomButtonSingleton` class like this:
|
||||
Mira API handles all other tasks and logic required to add the button to the game.
|
||||
|
||||
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.
|
||||
The button API is straightforward, but provides a lot of flexibility.
|
||||
There are various methods you can override to customize the behavior 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).
|
||||
|
||||
@@ -209,7 +230,7 @@ An example button can be found [here](https://github.com/All-Of-Us-Mods/MiraAPI/
|
||||
|
||||
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.
|
||||
Creating custom colors isn't difficult, but there are some requirements for Mira to register your colors.
|
||||
|
||||
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.
|
||||
@@ -230,13 +251,17 @@ public static class MyCustomColors
|
||||
|
||||
## 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.
|
||||
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.
|
||||
|
||||
Mira API comes with two asset loaders:
|
||||
Mira API comes with a few asset loaders:
|
||||
1. `LoadableBundleAsset<T>`: This is used for loading assets from AssetBundles.
|
||||
2. `LoadableResourceAsset`: This is used for loading **only sprites** from the Embedded Resources within a mod.
|
||||
2. `LoadableAddressableAsset<T>`: This is used for loading assets from Addressables.
|
||||
3. `LoadableResourceAsset`: This is used for loading **only sprites** from the Embedded Resources within a mod.
|
||||
4. `LoadableAudioResourceAsset`: This is used for loading **only audio clips** from the Embedded Resources within a mod.
|
||||
|
||||
The code below shows how to use these asset loaders:
|
||||
The code below shows how to use an asset loaders:
|
||||
```csharp
|
||||
// Load a sprite from an AssetBundle
|
||||
AssetBundle bundle = AssetBundleManager.Load("MyBundle"); // AssetBundleManager is a utility provided by Reactor
|
||||
@@ -252,7 +277,3 @@ Sprite button = buttonSpriteAsset.LoadAsset();
|
||||
You can view an example file [here](https://github.com/All-Of-Us-Mods/MiraAPI/blob/master/MiraAPI.Example/ExampleAssets.cs).
|
||||
|
||||
You can create your own asset loaders by inheriting from `LoadableAsset<T>` and implementing the `LoadAsset` method.
|
||||
|
||||
# Disclaimer
|
||||
|
||||
> This mod is not affiliated with Among Us or Innersloth LLC, and the content contained therein is not endorsed or otherwise sponsored by Innersloth LLC. Portions of the materials contained herein are property of Innersloth LLC. © Innersloth LLC.
|
||||
Reference in new issue
Block a user