mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-20 01:46:15 +00:00
Compare commits
59
Commits
0.2.0-rc.7
..
0.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08ca995eb1 | ||
|
|
453259df98 | ||
|
|
2ec3eec370 | ||
|
|
2ee8730ba4 | ||
|
|
c89e5938cc | ||
|
|
01fa1f0915 | ||
|
|
355eef509b | ||
|
|
763a493283 | ||
|
|
68e421bf91 | ||
|
|
c343ab5e9f | ||
|
|
9c1ef70ea7 | ||
|
|
c0c8fa08ff | ||
|
|
e7fba386aa | ||
|
|
72e5e0c137 | ||
|
|
4422072060 | ||
|
|
8145c8e599 | ||
|
|
afd6fbc3b6 | ||
|
|
74741b41a7 | ||
|
|
f98429b5f9 | ||
|
|
bf7edb30ab | ||
|
|
b4527014b7 | ||
|
|
277391ef1c | ||
|
|
dfe124d67a | ||
|
|
2b06633488 | ||
|
|
4784b1f0ad | ||
|
|
d9d0d6bc62 | ||
|
|
6b3038f397 | ||
|
|
4e1db039a0 | ||
|
|
62bee24789 | ||
|
|
2156c80467 | ||
|
|
0ff60071c2 | ||
|
|
de2f3f0d95 | ||
|
|
05f132c506 | ||
|
|
ab54c44434 | ||
|
|
bf652b68c3 | ||
|
|
00377c7926 | ||
|
|
55555604f3 | ||
|
|
de77a9fa92 | ||
|
|
eba2a037d3 | ||
|
|
bc7f772de9 | ||
|
|
f43191c4f2 | ||
|
|
cd818a18cf | ||
|
|
56d4e399d9 | ||
|
|
0d41ebbc13 | ||
|
|
9388ff487a | ||
|
|
720491e5cf | ||
|
|
9884df356e | ||
|
|
7ec4addacc | ||
|
|
bc9bc0ce24 | ||
|
|
aff1c311fa | ||
|
|
0d2a9d9700 | ||
|
|
669c541279 | ||
|
|
2e5f9639b3 | ||
|
|
e6d1f440c8 | ||
|
|
2ac8bdc240 | ||
|
|
d5d99001ea | ||
|
|
f5b4bdd1d4 | ||
|
|
cd6c4f5390 | ||
|
|
5cc29d903a |
No files matched your search
@@ -8,7 +8,7 @@
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.2.0</VersionPrefix>
|
||||
<VersionPrefix>0.2.1</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
|
||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||
|
||||
@@ -6,6 +6,7 @@ using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Rewired;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons.Freezer;
|
||||
@@ -19,6 +20,8 @@ public class FreezeButton : CustomActionButton<PlayerControl>
|
||||
public override int MaxUses => (int)OptionGroupSingleton<FreezerRoleSettings>.Instance.FreezeUses;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.T;
|
||||
public override ModifierKey Modifier1 => ModifierKey.Control;
|
||||
|
||||
protected override void OnClick()
|
||||
{
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Rewired;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
@@ -15,6 +16,9 @@ public class MeetingButton : CustomActionButton
|
||||
public override int MaxUses => 3;
|
||||
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.P;
|
||||
public override ModifierKey Modifier1 => ModifierKey.Control;
|
||||
public override ModifierKey Modifier2 => ModifierKey.Shift;
|
||||
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using MiraAPI.Example.Roles;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Rewired;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons;
|
||||
@@ -12,6 +13,7 @@ public class NeutralKillerButton : CustomActionButton
|
||||
public override string Name => "Win Game";
|
||||
public override float Cooldown => 0f;
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.ExampleButton;
|
||||
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.K;
|
||||
protected override void OnClick()
|
||||
{
|
||||
CustomGameOver.Trigger<NeutralKillerGameOver>([PlayerControl.LocalPlayer.Data]);
|
||||
|
||||
@@ -5,6 +5,7 @@ using MiraAPI.GameOptions;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Example.Buttons.Teleporter;
|
||||
@@ -21,7 +22,7 @@ public class TeleportButton : CustomActionButton
|
||||
public override LoadableAsset<Sprite> Sprite => ExampleAssets.TeleportButton;
|
||||
public override Color TextOutlineColor => new Color32(221, 176, 152, 255);
|
||||
public static bool IsZoom { get; private set; }
|
||||
|
||||
public override KeyboardKeyCode Defaultkeybind => KeyboardKeyCode.M;
|
||||
public override bool Enabled(RoleBehaviour? role)
|
||||
{
|
||||
return role is TeleporterRole;
|
||||
|
||||
@@ -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));
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Event invoked on IntroCutscene.OnDestroy and ExileController.WrapUp to determine if it should run the round event or not.
|
||||
/// </summary>
|
||||
public class BeforeRoundStartEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the event was triggered by the IntroCutscene or ExileController.
|
||||
/// </summary>
|
||||
public bool TriggeredByIntro { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BeforeRoundStartEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="triggeredByIntro">Whether the event was triggered by the intro or not.</param>
|
||||
public BeforeRoundStartEvent(bool triggeredByIntro)
|
||||
{
|
||||
TriggeredByIntro = triggeredByIntro;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
/// <summary>
|
||||
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp.
|
||||
/// Start Round event, invoked on IntroCutscene.OnDestroy and ExileController.WrapUp if the event isn't cancelled.
|
||||
/// </summary>
|
||||
public class RoundStartEvent : MiraEvent
|
||||
{
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Voting;
|
||||
|
||||
namespace MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
|
||||
/// <summary>
|
||||
/// Invoked before the dummy selects their vote. If canceled, the dummy won't vote.
|
||||
/// </summary>
|
||||
public class DummyVoteEvent : MiraCancelableEvent
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the dummy that needs to vote.
|
||||
/// </summary>
|
||||
public DummyBehaviour Dummy { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the dummy's vote data.
|
||||
/// </summary>
|
||||
public PlayerVoteData VoteData { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the dummy can vote to skip the meeting.
|
||||
/// </summary>
|
||||
public bool CanSkip { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a predicate for validating whether the dummy can vote on a player.
|
||||
/// By default, excludes dead and disconnected players.
|
||||
/// </summary>
|
||||
public Predicate<PlayerControl> PlayerIsValid { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DummyVoteEvent"/> class.
|
||||
/// </summary>
|
||||
/// <param name="dummy">The voting dummy.</param>
|
||||
public DummyVoteEvent(DummyBehaviour dummy)
|
||||
{
|
||||
Dummy = dummy;
|
||||
VoteData = dummy.myPlayer.GetVoteData();
|
||||
CanSkip = true;
|
||||
PlayerIsValid = x => !x.Data.IsDead && !x.Data.Disconnected;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using MiraAPI.Events.Mira;
|
||||
using MiraAPI.Patches;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Rewired;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
@@ -52,6 +53,28 @@ public abstract class CustomActionButton
|
||||
/// </summary>
|
||||
public virtual int MaxUses => 0;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current key binding for this button.
|
||||
/// </summary>
|
||||
public abstract KeyboardKeyCode Defaultkeybind { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The first optional modifier key (e.g., Control, Shift, Alt) that must be held with the main key to activate the button.
|
||||
/// </summary>
|
||||
public virtual ModifierKey Modifier1 => ModifierKey.None;
|
||||
|
||||
/// <summary>
|
||||
/// The second optional modifier key.
|
||||
/// Set to <see cref="ModifierKey.None"/> if unused.
|
||||
/// </summary>
|
||||
public virtual ModifierKey Modifier2 => ModifierKey.None;
|
||||
|
||||
/// <summary>
|
||||
/// The third optional modifier key.
|
||||
/// Set to <see cref="ModifierKey.None"/> if unused.
|
||||
/// </summary>
|
||||
public virtual ModifierKey Modifier3 => ModifierKey.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's text outline color.
|
||||
/// </summary>
|
||||
@@ -184,7 +207,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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds
|
||||
{
|
||||
/// <summary>
|
||||
/// Lets you register and manage mod keybinds with conflict checks and rebinding support.
|
||||
/// </summary>
|
||||
public static class KeybindManager
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores all info for a registered keybind.
|
||||
/// </summary>
|
||||
public class KeybindEntry
|
||||
{
|
||||
/// <summary>Unique identifier for this keybind.</summary>
|
||||
public string Id;
|
||||
|
||||
/// <summary>Text shown in the Among Us rebinding menu to describe this keybind.</summary>
|
||||
public string Description;
|
||||
|
||||
/// <summary>
|
||||
/// The method that runs when this keybind is pressed.
|
||||
/// </summary>
|
||||
public Action Handler;
|
||||
|
||||
/// <summary>
|
||||
/// The key currently assigned to this keybind.
|
||||
/// </summary>
|
||||
public KeyboardKeyCode Key;
|
||||
|
||||
/// <summary>
|
||||
/// If true, this keybind will be checked for conflicts with other exclusive keybinds using the same key.
|
||||
/// </summary>
|
||||
public bool Exclusive;
|
||||
|
||||
/// <summary>
|
||||
/// The first optional modifier key (e.g., Control, Shift, Alt) that must be held with the main key.
|
||||
/// </summary>
|
||||
public ModifierKey Modifier1 = ModifierKey.None;
|
||||
|
||||
/// <summary>
|
||||
/// The second optional modifier key, if needed. Set to <c>ModifierKey.None</c> if unused.
|
||||
/// </summary>
|
||||
public ModifierKey Modifier2 = ModifierKey.None;
|
||||
|
||||
/// <summary>
|
||||
/// The third optional modifier key, if needed. Set to <c>ModifierKey.None</c> if unused.
|
||||
/// </summary>
|
||||
public ModifierKey Modifier3 = ModifierKey.None;
|
||||
}
|
||||
|
||||
private static readonly List<KeybindEntry> Registered = new();
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new mod keybind.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique identifier for the keybind.</param>
|
||||
/// <param name="description">Description to display in UI.</param>
|
||||
/// <param name="key">Default key to use if no rebind exists.</param>
|
||||
/// <param name="handler">Method to invoke when the keybind is pressed.</param>
|
||||
/// <param name="exclusive">If true, conflicts with other exclusive keybinds on the same key.</param>
|
||||
/// <param name="modifier1">Optional first modifier key (e.g., <c>Control</c>, <c>Shift</c>, <c>Alt</c>) that must be held with the main key.</param>
|
||||
/// <param name="modifier2">Optional second modifier key. Use <c>ModifierKey.None</c> if unused.</param>
|
||||
/// <param name="modifier3">Optional third modifier key. Use <c>ModifierKey.None</c> if unused.</param>
|
||||
/// <returns> Returns the key that was registered for this keybind. </returns>
|
||||
public static KeyboardKeyCode Register(string id, string description, KeyboardKeyCode key, Action handler, bool exclusive = true, ModifierKey modifier1 = ModifierKey.None, ModifierKey modifier2 = ModifierKey.None, ModifierKey modifier3 = ModifierKey.None)
|
||||
{
|
||||
Registered.Add(new KeybindEntry
|
||||
{
|
||||
Id = id,
|
||||
Description = description,
|
||||
Key = key,
|
||||
Handler = handler,
|
||||
Exclusive = exclusive,
|
||||
Modifier1 = modifier1,
|
||||
Modifier2 = modifier2,
|
||||
Modifier3 = modifier3,
|
||||
});
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all conflicts where exclusive keybinds use the same key.
|
||||
/// </summary>
|
||||
/// <returns>A list of pairs of keybinds that conflict.</returns>
|
||||
public static List<(KeybindEntry, KeybindEntry)> GetConflicts()
|
||||
{
|
||||
var conflicts = new List<(KeybindEntry, KeybindEntry)>();
|
||||
for (int i = 0; i < Registered.Count; i++)
|
||||
{
|
||||
for (int j = i + 1; j < Registered.Count; j++)
|
||||
{
|
||||
var a = Registered[i];
|
||||
var b = Registered[j];
|
||||
if (a.Key == b.Key && a.Exclusive && b.Exclusive)
|
||||
{
|
||||
conflicts.Add((a, b));
|
||||
}
|
||||
}
|
||||
}
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns all currently registered keybind entries.
|
||||
/// </summary>
|
||||
/// <returns>A list of all registered keybind entries.</returns>
|
||||
public static List<KeybindEntry> GetEntries()
|
||||
{
|
||||
return Registered;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
using Reactor;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
/// <summary>
|
||||
/// Mira API Config File Handler
|
||||
/// </summary>
|
||||
public class MiraApiConfig(ConfigFile config)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets whether the modifiers hud should be on the left side of the screen (under roles/task tab). Recommended for streamers.
|
||||
/// </summary>
|
||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
||||
}
|
||||
@@ -30,6 +30,10 @@ public partial class MiraApiPlugin : BasePlugin
|
||||
/// </summary>
|
||||
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Mira Api Config Handler.
|
||||
/// </summary>
|
||||
public MiraApiConfig? MiraConfig { get; private set; }
|
||||
private static MiraPluginManager? PluginManager { get; set; }
|
||||
internal Harmony Harmony { get; } = new(Id);
|
||||
|
||||
@@ -40,6 +44,7 @@ public partial class MiraApiPlugin : BasePlugin
|
||||
|
||||
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
|
||||
|
||||
MiraConfig = new MiraApiConfig(Config);
|
||||
PluginManager = new MiraPluginManager();
|
||||
PluginManager.Initialize();
|
||||
}
|
||||
|
||||
@@ -64,6 +64,11 @@ public abstract class BaseModifier : IOptionable
|
||||
/// </summary>
|
||||
public virtual bool ShowInFreeplay => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating the color that should be used for the modifier within freeplay.
|
||||
/// </summary>
|
||||
public virtual Color FreeplayFileColor => Color.gray;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the modifier is unique. If true, the player can only have one instance of this modifier.
|
||||
/// </summary>
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Linq;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Modifiers.ModifierDisplay;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.Patches.Roles;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
@@ -126,7 +127,18 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
|
||||
if (_player.AmOwner && ModifierDisplay && HudManager.InstanceExists)
|
||||
{
|
||||
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
var taskPanelOpen = HudManager.Instance.TaskPanel.open;
|
||||
var roleTabOpen = HudManagerPatches.RoleTab != null && HudManagerPatches.RoleTab.open;
|
||||
var leftSideHud = PluginSingleton<MiraApiPlugin>.Instance.MiraConfig!.ModifiersHudLeftSide.Value;
|
||||
|
||||
if (leftSideHud && (taskPanelOpen || roleTabOpen))
|
||||
{
|
||||
ModifierDisplay!.gameObject.SetActive(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
ModifierDisplay!.gameObject.SetActive(MeetingHud.Instance || HudManager.Instance.TaskPanel.isActiveAndEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Patches.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
@@ -94,6 +97,16 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
|
||||
IsOpen = false;
|
||||
_children.gameObject.SetActive(false);
|
||||
|
||||
if (PluginSingleton<MiraApiPlugin>.Instance.MiraConfig!.ModifiersHudLeftSide.Value)
|
||||
{
|
||||
var aspect = GetComponent<AspectPosition>();
|
||||
_toggleButton.transform.localPosition = new Vector3(-1f, 2.7f, 0f);
|
||||
_children.transform.localPosition = new Vector3(-0.2f, -0.05f, 0f);
|
||||
aspect.Alignment = AspectPosition.EdgeAlignments.LeftTop;
|
||||
aspect.DistanceFromEdge = new Vector3(1.8f, 2.55f, -20f);
|
||||
aspect.AdjustPosition();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -109,7 +122,16 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
/// </summary>
|
||||
public void ToggleTab()
|
||||
{
|
||||
IsOpen = !IsOpen;
|
||||
SetOpened(!IsOpen);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use this to set the visibility of the modifiers tab.
|
||||
/// </summary>
|
||||
/// <param name="val">Whether it should be visible or not.</param>
|
||||
public void SetOpened(bool val)
|
||||
{
|
||||
IsOpen = val;
|
||||
_children.gameObject.SetActive(IsOpen);
|
||||
RefreshModifiers();
|
||||
}
|
||||
@@ -118,7 +140,6 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
_modifiers.Remove(component.Modifier!);
|
||||
component.gameObject.DestroyImmediate();
|
||||
|
||||
RefreshModifiers();
|
||||
}
|
||||
|
||||
@@ -194,13 +215,7 @@ public class ModifierDisplayComponent(nint cppPtr) : MonoBehaviour(cppPtr)
|
||||
|
||||
_toggleBtnText.text = $"Modifiers ({filteredModifiers.Count})";
|
||||
|
||||
if (filteredModifiers.Count == 0)
|
||||
{
|
||||
_toggleButton.gameObject.SetActive(false);
|
||||
return;
|
||||
}
|
||||
|
||||
_toggleButton.gameObject.SetActive(true);
|
||||
_toggleButton.gameObject.SetActive(filteredModifiers.Count != 0);
|
||||
|
||||
foreach (var mod in modifiersToAdd)
|
||||
{
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Runtime.Serialization;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using Random = System.Random;
|
||||
@@ -16,6 +17,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 +88,7 @@ public static class ModifierManager
|
||||
}
|
||||
|
||||
info.InternalModifiers.Add(modifier);
|
||||
InternalModifiers.Add(modifier);
|
||||
|
||||
if (modifier is not GameModifier gameModifier)
|
||||
{
|
||||
@@ -98,7 +112,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();
|
||||
|
||||
@@ -108,6 +126,7 @@ public static class ModifierManager
|
||||
.Select(x => Activator.CreateInstance(x.Value) as GameModifier)
|
||||
.OfType<GameModifier>()
|
||||
.Where(x => x.GetAmountPerGame() > 0 && x.GetAssignmentChance() > 0)
|
||||
.Shuffle()
|
||||
.OrderByDescending(x => x.Priority())
|
||||
.ToArray();
|
||||
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
#pragma warning disable SA1629
|
||||
/// <summary>
|
||||
/// Allows players to paste text into chat.
|
||||
/// Source: https://github.com/CallOfCreator/NewMod/blob/main/NewMod/Patches/ClipboardPatch.cs
|
||||
/// </summary>
|
||||
#pragma warning restore SA1629
|
||||
[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);
|
||||
}
|
||||
}
|
||||
@@ -12,14 +12,16 @@ public static class CanUsePatches
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
yield return AccessTools.Method(typeof(Console), nameof(Console.CanUse));
|
||||
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
|
||||
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(DeconControl), nameof(DeconControl.CanUse));
|
||||
yield return AccessTools.Method(typeof(DoorConsole), nameof(DoorConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(Ladder), nameof(Ladder.CanUse));
|
||||
yield return AccessTools.Method(typeof(MapConsole), nameof(MapConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(OpenDoorConsole), nameof(OpenDoorConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(OptionsConsole), nameof(OptionsConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(PlatformConsole), nameof(PlatformConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(SystemConsole), nameof(SystemConsole.CanUse));
|
||||
yield return AccessTools.Method(typeof(ZiplineConsole), nameof(ZiplineConsole.CanUse));
|
||||
// vent is not necessary as it has its own patch
|
||||
}
|
||||
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch(typeof(TutorialManager._RunTutorial_d__3), nameof(TutorialManager._RunTutorial_d__3.MoveNext))]
|
||||
public static class FreeplayRoundStartPatch
|
||||
{
|
||||
public static void Postfix(TutorialManager._RunTutorial_d__3 __instance, ref bool __result)
|
||||
{
|
||||
if (!__result)
|
||||
{
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,12 @@ internal static class SpawnInMinigamePatch
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
var @event = new RoundStartEvent(false);
|
||||
var @event = new BeforeRoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
|
||||
var @event2 = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event2);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,12 @@ internal static class SubmergedSpawnGamePatch
|
||||
|
||||
public static void Postfix()
|
||||
{
|
||||
var @event = new RoundStartEvent(false);
|
||||
var @event = new BeforeRoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
|
||||
var @event2 = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event2);
|
||||
}
|
||||
}
|
||||
@@ -22,15 +22,4 @@ internal static class TaskAddButtonPatches
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(TaskAddButton.Role), MethodType.Setter)]
|
||||
public static void RoleGetterPatch(TaskAddButton __instance)
|
||||
{
|
||||
if (__instance.role is ICustomRole { Team: ModdedRoleTeams.Custom } customRole)
|
||||
{
|
||||
__instance.FileImage.color = customRole.IntroConfiguration?.IntroTeamColor ?? Color.gray;
|
||||
}
|
||||
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@@ -19,7 +20,6 @@ namespace MiraAPI.Patches.Freeplay;
|
||||
internal static class TaskAdderPatches
|
||||
{
|
||||
private static Scroller _scroller = null!;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(TaskAdderGame.Begin))]
|
||||
public static void AddRolesFolder(TaskAdderGame __instance)
|
||||
@@ -32,6 +32,11 @@ internal static class TaskAdderPatches
|
||||
_scroller.allowY = true;
|
||||
_scroller.Inner = inner.transform;
|
||||
|
||||
var scrollerHelper = __instance.TaskParent.gameObject.AddComponent<ManualScrollHelper>();
|
||||
scrollerHelper.scroller = _scroller;
|
||||
scrollerHelper.verticalAxis = RewiredConstsEnum.Action.TaskRVertical;
|
||||
scrollerHelper.scrollSpeed = 10f;
|
||||
|
||||
GameObject hitbox = new("Hitbox")
|
||||
{
|
||||
layer = 5,
|
||||
@@ -295,7 +300,7 @@ internal static class TaskAdderPatches
|
||||
|
||||
if (_scroller)
|
||||
{
|
||||
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 5, 1.4f);
|
||||
_scroller.CalculateAndSetYBounds(__instance.ActiveItems.Count, 6, 4.5f, 1.65f);
|
||||
_scroller.SetYBoundsMin(0.0f);
|
||||
_scroller.ScrollToTop();
|
||||
}
|
||||
@@ -332,7 +337,8 @@ internal static class TaskAdderPatches
|
||||
taskAddButton.SafePositionWorld = instance.SafePositionWorld;
|
||||
taskAddButton.Text.text = modifier.ModifierName;
|
||||
taskAddButton.Text.EnableMasking();
|
||||
|
||||
taskAddButton.FileImage.color = modifier.FreeplayFileColor;
|
||||
taskAddButton.RolloverHandler.OutColor = modifier.FreeplayFileColor;
|
||||
taskAddButton.Button.OnClick = new Button.ButtonClickedEvent();
|
||||
taskAddButton.Button.OnClick.AddListener((UnityEngine.Events.UnityAction)(() =>
|
||||
{
|
||||
@@ -384,6 +390,20 @@ internal static class TaskAdderPatches
|
||||
|
||||
instance.AddFileAsChildCustom(taskAddButton2, ref num, ref num2, ref num3);
|
||||
taskAddButton2.Role = roleBehaviour;
|
||||
if (roleBehaviour is ICustomRole custom)
|
||||
{
|
||||
var customColor = custom.IntroConfiguration?.IntroTeamColor ?? Color.gray;
|
||||
if (custom.Team is ModdedRoleTeams.Crewmate)
|
||||
{
|
||||
customColor = Palette.CrewmateBlue;
|
||||
}
|
||||
else if (custom.Team is ModdedRoleTeams.Impostor)
|
||||
{
|
||||
customColor = Palette.ImpostorRed;
|
||||
}
|
||||
taskAddButton2.FileImage.color = customColor;
|
||||
taskAddButton2.RolloverHandler.OutColor = customColor;
|
||||
}
|
||||
if (taskAddButton2.Button == null)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Keybinds;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
/// <summary>
|
||||
/// Checks keybind conflicts in the lobby and disables secondary ones.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(GameStartManager))]
|
||||
public static class GameStartManagerPatch
|
||||
{
|
||||
public static bool conflictShown = false;
|
||||
|
||||
[HarmonyPatch(nameof(GameStartManager.Start))]
|
||||
[HarmonyPostfix]
|
||||
public static void StartPostfix(GameStartManager __instance)
|
||||
{
|
||||
if (conflictShown) return;
|
||||
|
||||
var conflicts = KeybindManager.GetConflicts();
|
||||
if (conflicts.Count == 0) return;
|
||||
|
||||
conflictShown = true;
|
||||
|
||||
var message = "The following keybinds are disabled due to conflicts:\n\n";
|
||||
|
||||
foreach (var (a, b) in conflicts)
|
||||
{
|
||||
message += $"• <color=#FF5555>{b.Description}</color> (conflicts with {a.Description})\n";
|
||||
b.Handler = null;
|
||||
}
|
||||
|
||||
HudManager.Instance.ShowPopUp(message.TrimEnd());
|
||||
}
|
||||
}
|
||||
@@ -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,6 +1,11 @@
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Keybinds;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using Rewired;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@@ -77,7 +82,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 +117,36 @@ 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(HudManager.Update))]
|
||||
[HarmonyPostfix]
|
||||
public static void StartPostfix()
|
||||
{
|
||||
if (AmongUsClient.Instance.GameState != InnerNet.InnerNetClient.GameStates.Started) return;
|
||||
if (PlayerControl.LocalPlayer.Data.IsDead) return;
|
||||
|
||||
foreach (var entry in KeybindManager.GetEntries())
|
||||
{
|
||||
var player = ReInput.players.GetPlayer(0);
|
||||
var keyboard = player.controllers.Keyboard;
|
||||
|
||||
if (player.GetButtonDown(entry.Id) &&
|
||||
(entry.Modifier1 == ModifierKey.None || keyboard.GetModifierKey(entry.Modifier1)) &&
|
||||
(entry.Modifier2 == ModifierKey.None || keyboard.GetModifierKey(entry.Modifier2)) &&
|
||||
(entry.Modifier3 == ModifierKey.None || keyboard.GetModifierKey(entry.Modifier3)))
|
||||
{
|
||||
entry.Handler?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,6 +61,11 @@ public static class IntroCutscenePatches
|
||||
public static void GameBeginPatch(IntroCutscene __instance)
|
||||
{
|
||||
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
|
||||
|
||||
var @event = new BeforeRoundStartEvent(true);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.Utilities;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Patches.Keybinds
|
||||
{
|
||||
[HarmonyPatch(typeof(InputManager_Base), nameof(InputManager_Base.Awake))]
|
||||
public static class KeybindMenuPatch
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
private static void StartPrefix(InputManager_Base __instance)
|
||||
{
|
||||
foreach (var entry in KeybindManager.GetEntries())
|
||||
{
|
||||
__instance.userData.RegisterModBind(entry.Id, entry.Description, entry.Key, modifier1: entry.Modifier1, modifier2: entry.Modifier2, modifier3: entry.Modifier3);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -84,7 +84,7 @@ public static class HatsTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{sortedHats.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
@@ -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;
|
||||
|
||||
@@ -73,7 +73,7 @@ public static class NameplatesTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{SortedNameplates.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ public static class SkinsTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{sortedSkins.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ public static class VisorsTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName}\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{SortedVisors.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,8 +38,13 @@ internal static class EjectionPatches
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
var @event = new RoundStartEvent(false);
|
||||
var @event = new BeforeRoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
|
||||
var @event2 = new RoundStartEvent(false);
|
||||
MiraEventManager.InvokeEvent(@event2);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace MiraAPI.Patches.Roles;
|
||||
public static class HudManagerPatches
|
||||
{
|
||||
// Custom role tab.
|
||||
private static TaskPanelBehaviour? _roleTab;
|
||||
public static TaskPanelBehaviour? RoleTab;
|
||||
|
||||
/// <summary>
|
||||
/// Fixes Kill Button not showing for Neutral killing role.
|
||||
@@ -38,9 +38,9 @@ public static class HudManagerPatches
|
||||
__instance.SabotageButton.gameObject.SetActive(isActive && customRole.Configuration.CanUseSabotage);
|
||||
}
|
||||
|
||||
if (_roleTab)
|
||||
if (RoleTab)
|
||||
{
|
||||
_roleTab?.gameObject.SetActive(isActive);
|
||||
RoleTab?.gameObject.SetActive(isActive);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,16 +62,16 @@ public static class HudManagerPatches
|
||||
|
||||
if (role is ICustomRole { Configuration.RoleHintType: RoleHintType.RoleTab } customRole)
|
||||
{
|
||||
if (_roleTab == null)
|
||||
if (RoleTab == null)
|
||||
{
|
||||
_roleTab = CustomRoleManager.CreateRoleTab(customRole);
|
||||
RoleTab = CustomRoleManager.CreateRoleTab(customRole);
|
||||
}
|
||||
|
||||
CustomRoleManager.UpdateRoleTab(_roleTab, customRole);
|
||||
CustomRoleManager.UpdateRoleTab(RoleTab, customRole);
|
||||
}
|
||||
else if (_roleTab)
|
||||
else if (RoleTab)
|
||||
{
|
||||
_roleTab?.gameObject.Destroy();
|
||||
RoleTab?.gameObject.Destroy();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,59 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
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)
|
||||
{
|
||||
var findingGame = SceneManager.GetActiveScene().name is "FindAGame";
|
||||
|
||||
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;
|
||||
if (findingGame)
|
||||
{
|
||||
x += 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(
|
||||
findingGame ? 2f : 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;
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,79 @@
|
||||
using HarmonyLib;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BepInEx.Unity.IL2CPP.Utils.Collections;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Meeting.Voting;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Voting;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Voting;
|
||||
|
||||
[HarmonyPatch(typeof(DummyBehaviour), nameof(DummyBehaviour.Update))]
|
||||
internal static class DummyBehaviourPatch
|
||||
[HarmonyPatch(typeof(DummyBehaviour))]
|
||||
internal static class DummyBehaviourPatches
|
||||
{
|
||||
public static void Postfix(DummyBehaviour __instance)
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(DummyBehaviour.Update))]
|
||||
public static bool DummyUpdatePatch(DummyBehaviour __instance)
|
||||
{
|
||||
if (MeetingHud.Instance && __instance.myPlayer.GetVoteData())
|
||||
NetworkedPlayerInfo data = __instance.myPlayer.Data;
|
||||
if (data == null || data.IsDead) return false;
|
||||
|
||||
if (MeetingHud.Instance)
|
||||
{
|
||||
__instance.voted = __instance.myPlayer.GetVoteData().VotesRemaining == 0;
|
||||
if (!__instance.voted)
|
||||
{
|
||||
__instance.StartCoroutine(CoDoVote(__instance).WrapToIl2Cpp());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.voted = false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerator CoDoVote(DummyBehaviour dummy)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning(dummy.voted);
|
||||
dummy.voted = true;
|
||||
yield return new WaitForSeconds(dummy.voteTime.Next());
|
||||
|
||||
var dummyVoteEvent = new DummyVoteEvent(dummy);
|
||||
MiraEventManager.InvokeEvent(dummyVoteEvent);
|
||||
|
||||
if (dummyVoteEvent.IsCancelled)
|
||||
{
|
||||
dummy.voted = false;
|
||||
yield break;
|
||||
}
|
||||
|
||||
List<byte> potentialSuspects = new();
|
||||
potentialSuspects.AddRange(PlayerControl.AllPlayerControls
|
||||
.ToArray()
|
||||
.Where(p => p != dummy.myPlayer)
|
||||
.Where(p => dummyVoteEvent.PlayerIsValid(p))
|
||||
.Select(p => p.PlayerId));
|
||||
|
||||
if (dummyVoteEvent.CanSkip)
|
||||
{
|
||||
potentialSuspects.Add(253);
|
||||
}
|
||||
|
||||
if (CanVote(dummy))
|
||||
{
|
||||
VotingUtils.RpcCastVote(PlayerControl.LocalPlayer, dummy.myPlayer.PlayerId, potentialSuspects.Random());
|
||||
}
|
||||
}
|
||||
|
||||
private static bool CanVote(this DummyBehaviour dummy)
|
||||
{
|
||||
var dummyVoteData = dummy.myPlayer.GetVoteData();
|
||||
return dummyVoteData.VotesRemaining > 0;
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,14 @@ using MiraAPI.GameEnd;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.GameOptions.Attributes;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Presets;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.PluginLoading;
|
||||
|
||||
@@ -129,7 +131,16 @@ public sealed class MiraPluginManager
|
||||
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);
|
||||
|
||||
foreach (var button in CustomButtonManager.Buttons)
|
||||
{
|
||||
KeybindManager.Register($"{button.Name}_Keybind", $"Keybind for {button.Name}", button.Defaultkeybind, button.ClickHandler, modifier1: button.Modifier1, modifier2: button.Modifier2, modifier3: button.Modifier3);
|
||||
|
||||
Logger<MiraApiPlugin>.Info($"Registered keybind for button '{button.GetType().Name}' with default key {button.Defaultkeybind}.");
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Extensions for IEnumerable.
|
||||
/// </summary>
|
||||
public static class EnumerableExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Shuffle the elements of the source enumerable using a new instance of Random.
|
||||
/// </summary>
|
||||
/// <param name="source">The source enumerable.</param>
|
||||
/// <typeparam name="T">The type of the elements.</typeparam>
|
||||
/// <returns>>The shuffled enumerable.</returns>
|
||||
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
|
||||
{
|
||||
return source.Shuffle(new Random());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shuffle the elements of the source enumerable using the provided Random instance.
|
||||
/// </summary>
|
||||
/// <param name="source">The source enumerable.</param>
|
||||
/// <param name="rng">The Random instance to use for shuffling.</param>
|
||||
/// <typeparam name="T">The type of the elements.</typeparam>
|
||||
/// <returns>>The shuffled enumerable.</returns>
|
||||
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(source);
|
||||
ArgumentNullException.ThrowIfNull(rng);
|
||||
|
||||
return source.ShuffleIterator(rng);
|
||||
}
|
||||
|
||||
private static IEnumerable<T> ShuffleIterator<T>(this IEnumerable<T> source, Random rng)
|
||||
{
|
||||
var buffer = source.ToList();
|
||||
for (var i = 0; i < buffer.Count; i++)
|
||||
{
|
||||
var j = rng.Next(i, buffer.Count);
|
||||
yield return buffer[j];
|
||||
|
||||
buffer[j] = buffer[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,8 @@ using MiraAPI.Networking;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Voting;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
using Rewired.Data;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -84,7 +86,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>
|
||||
@@ -92,7 +94,7 @@ public static class Extensions
|
||||
/// <param name="timer">The current timer value.</param>
|
||||
/// <param name="maxTimer">The maximum timer value.</param>
|
||||
/// <param name="format">The format string to use for the timer text.</param>
|
||||
public static void SetCooldownFormat(this ActionButton button, float timer, float maxTimer, string format="0")
|
||||
public static void SetCooldownFormat(this ActionButton button, float timer, float maxTimer, string format = "0")
|
||||
{
|
||||
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
||||
button.isCoolingDown = num > 0f;
|
||||
@@ -113,7 +115,7 @@ public static class Extensions
|
||||
/// <param name="timer">The current timer value.</param>
|
||||
/// <param name="maxTimer">The maximum timer value.</param>
|
||||
/// <param name="format">The format string to use for the timer text.</param>
|
||||
public static void SetFillUpFormat(this ActionButton button, float timer, float maxTimer, string format="0")
|
||||
public static void SetFillUpFormat(this ActionButton button, float timer, float maxTimer, string format = "0")
|
||||
{
|
||||
var num = Mathf.Clamp(timer / maxTimer, 0f, 1f);
|
||||
button.isCoolingDown = num > 0f;
|
||||
@@ -154,6 +156,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>
|
||||
@@ -495,6 +515,7 @@ public static class Extensions
|
||||
/// <param name="includeImpostors">Whether impostors should be included in the search.</param>
|
||||
/// <param name="distance">The radius to search within.</param>
|
||||
/// <param name="ignoreColliders">Whether colliders should be ignored when searching.</param>
|
||||
/// <param name="includeGhosts">Determines if Ghosts are included.</param>
|
||||
/// <param name="predicate">Optional predicate to test if the object is valid.</param>
|
||||
/// <returns>The closest player if there is one, false otherwise.</returns>
|
||||
public static PlayerControl? GetClosestPlayer(
|
||||
@@ -502,16 +523,16 @@ public static class Extensions
|
||||
bool includeImpostors,
|
||||
float distance,
|
||||
bool ignoreColliders = false,
|
||||
bool includeGhosts = false,
|
||||
Predicate<PlayerControl>? predicate = null)
|
||||
{
|
||||
var filteredPlayers = Helpers.GetClosestPlayers(playerControl, distance, ignoreColliders)
|
||||
.Where(
|
||||
playerInfo => !playerInfo.Data.Disconnected &&
|
||||
playerInfo.PlayerId != playerControl.PlayerId &&
|
||||
!playerInfo.Data.IsDead &&
|
||||
(includeGhosts || !playerInfo.Data.IsDead) &&
|
||||
(includeImpostors || !playerInfo.Data.Role.IsImpostor))
|
||||
.ToList();
|
||||
|
||||
return predicate != null ? filteredPlayers.Find(predicate) : filteredPlayers.FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -526,4 +547,49 @@ public static class Extensions
|
||||
renderer.material.SetColor(ShaderID.OutlineColor, color ?? Color.clear);
|
||||
renderer.material.SetColor(ShaderID.AddColor, color ?? Color.clear);
|
||||
}
|
||||
|
||||
// Inspired by: https://github.com/eDonnes124/Town-Of-Us-R/blob/master/source/Patches/Keybinds.cs#L29
|
||||
|
||||
/// <summary>
|
||||
/// Registers a new mod keybind as a user-assignable button action in Rewired.
|
||||
/// </summary>
|
||||
/// <param name="userData">The Rewired user data to add the action to.</param>
|
||||
/// <param name="actionName">The internal name of the action.</param>
|
||||
/// <param name="description">Text shown in the rebinding UI.</param>
|
||||
/// <param name="key">The default key to assign to this action.</param>
|
||||
/// <param name="category">Category ID to group actions in Rewired (default is 0).</param>
|
||||
/// <param name="elementIdentifierId">The element identifier ID (default is -1, meaning none specified).</param>
|
||||
/// <param name="type">The <see cref="InputActionType"/> for this action (default is Button).</param>
|
||||
/// <param name="modifier1">The first optional modifier key (e.g., <c>Control</c>, <c>Shift</c>, <c>Alt</c>) that must be held together with the main key.</param>
|
||||
/// <param name="modifier2">The second optional modifier key. Set to <see cref="ModifierKey.None"/> if not used.</param>
|
||||
/// <param name="modifier3">The third optional modifier key. Set to <see cref="ModifierKey.None"/> if not used.</param>
|
||||
/// <returns>The action ID of the newly registered action.</returns>
|
||||
public static int RegisterModBind(this UserData userData, string actionName, string description, KeyboardKeyCode key, int category = 0, int elementIdentifierId = -1, InputActionType type = InputActionType.Button, ModifierKey modifier1 = ModifierKey.None, ModifierKey modifier2 = ModifierKey.None, ModifierKey modifier3 = ModifierKey.None)
|
||||
{
|
||||
userData.AddAction(category);
|
||||
|
||||
var action = userData.GetAction(userData.actions.Count - 1)!;
|
||||
|
||||
action.name = actionName;
|
||||
action.descriptiveName = description;
|
||||
action.categoryId = category;
|
||||
action.type = type;
|
||||
action.userAssignable = true;
|
||||
|
||||
var map = new ActionElementMap
|
||||
{
|
||||
_elementIdentifierId = elementIdentifierId,
|
||||
_actionId = action.id,
|
||||
_elementType = ControllerElementType.Button,
|
||||
_axisContribution = Pole.Positive,
|
||||
_keyboardKeyCode = key,
|
||||
_modifierKey1 = modifier1,
|
||||
_modifierKey2 = modifier2,
|
||||
_modifierKey3 = modifier3,
|
||||
};
|
||||
userData.keyboardMaps[0].actionElementMaps.Add(map);
|
||||
userData.joystickMaps[0].actionElementMaps.Add(map);
|
||||
|
||||
return action.id;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,11 @@
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Il2CppSystem.IO;
|
||||
using MiraAPI.Keybinds;
|
||||
using Rewired;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
@@ -19,7 +23,7 @@ public static class Helpers
|
||||
/// <returns>A list of alive players.</returns>
|
||||
public static List<PlayerControl> GetAlivePlayers()
|
||||
{
|
||||
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x=>x.Object)];
|
||||
return [.. GameData.Instance.AllPlayers.ToArray().Where(x => !x.IsDead && !x.Disconnected && x.Object).Select(x => x.Object)];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,10 +40,10 @@ public static class Helpers
|
||||
case 100:
|
||||
return true;
|
||||
default:
|
||||
{
|
||||
var num = Random.RandomRangeInt(1, 101);
|
||||
return num <= probability;
|
||||
}
|
||||
{
|
||||
var num = UnityEngine.Random.RandomRangeInt(1, 101);
|
||||
return num <= probability;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +402,25 @@ public static class Helpers
|
||||
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
{
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray());
|
||||
.Select(s => s[UnityEngine.Random.RandomRangeInt(0, s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finds and returns an unused KeyCode that is not equal to the excluded key.
|
||||
/// </summary>
|
||||
/// <param name="exclude">The KeyCode to skip during the search.</param>
|
||||
/// <returns>
|
||||
/// The first available KeyCode not currently used by any registered keybind,
|
||||
/// or KeyCode.None if none are available.
|
||||
/// </returns>
|
||||
public static KeyboardKeyCode FindAvailableKey(KeyboardKeyCode exclude)
|
||||
{
|
||||
foreach (KeyboardKeyCode key in Enum.GetValues(typeof(KeyboardKeyCode)))
|
||||
{
|
||||
if (key == exclude) continue;
|
||||
bool used = KeybindManager.GetEntries().Exists(e => e.Key == key);
|
||||
if (!used) return key;
|
||||
}
|
||||
return KeyboardKeyCode.None;
|
||||
}
|
||||
}
|
||||
@@ -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