mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-19 15:51:48 +00:00
Begin seperation
This commit is contained in:
1 parent
0271430647
commit
1c25fd401b
41 files changed
+2728
No files matched your search
@@ -0,0 +1,138 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace MiraAPI.GameModes;
|
||||
|
||||
/// <summary>
|
||||
/// Base class for custom gamemodes
|
||||
/// </summary>
|
||||
public abstract class CustomGameMode
|
||||
{
|
||||
/// <summary>
|
||||
/// Game mode name
|
||||
/// </summary>
|
||||
public abstract string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Game mode description
|
||||
/// </summary>
|
||||
public abstract string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Game mode ID
|
||||
/// </summary>
|
||||
public abstract int Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when Intro Cutscene is destroyed
|
||||
/// </summary>
|
||||
public virtual void Initialize() { }
|
||||
|
||||
/// <summary>
|
||||
/// Called on HudManager.Start()
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance of HudManager</param>
|
||||
public virtual void HudStart(HudManager instance) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called every frame on HudManager.Update()
|
||||
/// </summary>
|
||||
/// <param name="instance">Instance of HudManager</param>
|
||||
public virtual void HudUpdate(HudManager instance) { }
|
||||
|
||||
/// <summary>
|
||||
/// Called when a player is killed
|
||||
/// </summary>
|
||||
/// <param name="player">PlayerControl that was killed</param>
|
||||
public virtual void OnDeath(PlayerControl player) { }
|
||||
|
||||
/// <summary>
|
||||
/// Calculate Game End logic
|
||||
/// </summary>
|
||||
/// <param name="runOriginal">Should original game end logic be used</param>
|
||||
/// <param name="instance">Instance of LogicGameFlowNormal</param>
|
||||
public virtual void CheckGameEnd(out bool runOriginal, LogicGameFlowNormal instance)
|
||||
{
|
||||
runOriginal = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom role assignment
|
||||
/// </summary>
|
||||
/// <param name="runOriginal">Should original role assignment be used</param>
|
||||
/// <param name="instance">Instance of LogicRoleSelection</param>
|
||||
public virtual void AssignRoles(out bool runOriginal, LogicRoleSelectionNormal instance)
|
||||
{
|
||||
runOriginal = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Custom murder logic
|
||||
/// </summary>
|
||||
/// <param name="runOriginal">Should original murder logic be used</param>
|
||||
/// <param name="result">Result of custom murder logic</param>
|
||||
/// <param name="target">Target player for murder</param>
|
||||
public virtual void CanKill(out bool runOriginal, out bool result, PlayerControl target)
|
||||
{
|
||||
result = false;
|
||||
runOriginal = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should Roles Settings be available when this gamemode is selected?
|
||||
/// </summary>
|
||||
public virtual bool CanAccessRolesTab() => true;
|
||||
|
||||
/// <summary>
|
||||
/// Should Game Settings be available when this gamemode is selected?
|
||||
/// </summary>
|
||||
public virtual bool CanAccessSettingsTab() => true;
|
||||
|
||||
/// <summary>
|
||||
/// Custom winner selection
|
||||
/// </summary>
|
||||
/// <returns>List of winners</returns>
|
||||
public virtual List<GameData.PlayerInfo> CalculateWinners() => null;
|
||||
|
||||
/// <summary>
|
||||
/// Show gamemode in Intro Cutscene
|
||||
/// </summary>
|
||||
public virtual bool ShowCustomRoleScreen() => false;
|
||||
|
||||
/// <summary>
|
||||
/// Can Admin be used in this gamemode
|
||||
/// </summary>
|
||||
/// <param name="console">Admin Console</param>
|
||||
public virtual bool CanUseMapConsole(MapConsole console) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Can a body be reported in this gamemode
|
||||
/// </summary>
|
||||
/// <param name="body">Target body for reporting</param>
|
||||
public virtual bool CanReport(DeadBody body) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Can system consoles be used in this gamemode
|
||||
/// </summary>
|
||||
/// <param name="console">System Console</param>
|
||||
public virtual bool CanUseSystemConsole(SystemConsole console) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Can tasks be interacted with in this gamemode
|
||||
/// </summary>
|
||||
/// <param name="console">Task console</param>
|
||||
public virtual bool CanUseConsole(Console console) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Should the sabotage map be used when attempting to open sabotage overlay
|
||||
/// </summary>
|
||||
/// <param name="map">MapBehaviour object</param>
|
||||
public virtual bool ShouldShowSabotageMap(MapBehaviour map) => true;
|
||||
|
||||
/// <summary>
|
||||
/// Can a player vent in this gamemode
|
||||
/// </summary>
|
||||
/// <param name="vent">Target vent</param>
|
||||
/// <param name="playerInfo">Player attempting to vent</param>
|
||||
public virtual bool CanVent(Vent vent, GameData.PlayerInfo playerInfo) => true;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.GameModes;
|
||||
|
||||
/// <summary>
|
||||
/// Manages custom gamemodes
|
||||
/// </summary>
|
||||
public static class CustomGameModeManager
|
||||
{
|
||||
/// <summary>
|
||||
/// List of registered gamemodes
|
||||
/// </summary>
|
||||
public static readonly Dictionary<int, CustomGameMode> GameModes = [];
|
||||
|
||||
public static bool IsDefault()
|
||||
{
|
||||
return ActiveMode.Id == 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current gamemode
|
||||
/// </summary>
|
||||
public static CustomGameMode ActiveMode;
|
||||
|
||||
/// <summary>
|
||||
/// Set current gamemode
|
||||
/// </summary>
|
||||
/// <param name="id">gamemode ID</param>
|
||||
public static void SetGameMode(int id)
|
||||
{
|
||||
if (GameModes.TryGetValue(id, out var gameMode))
|
||||
{
|
||||
ActiveMode = gameMode;
|
||||
return;
|
||||
}
|
||||
|
||||
Logger<MiraAPIPlugin>.Error($"No gamemode with id {id} found!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Register gamemode from type
|
||||
/// </summary>
|
||||
/// <param name="gameModeType">Type of gamemode class, should inherit from <see cref="CustomGameMode"/></param>
|
||||
public static void RegisterGameMode(Type gameModeType)
|
||||
{
|
||||
if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType))
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning($"{gameModeType?.Name} does not inherit CustomGameMode!");
|
||||
return;
|
||||
}
|
||||
|
||||
var gameMode = (CustomGameMode)Activator.CreateInstance(gameModeType);
|
||||
|
||||
if (GameModes.Any(x => x.Key == gameMode?.Id))
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Error($"ID for gamemode {gameMode?.Name} already exists!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (gameMode == null)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Error($"GAMEMODE WITH TYPE {gameModeType.Name} IS NULL");
|
||||
return;
|
||||
}
|
||||
|
||||
GameModes.Add(gameMode.Id, gameMode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
namespace MiraAPI.GameModes;
|
||||
|
||||
[RegisterGameMode]
|
||||
public class DefaultMode : CustomGameMode
|
||||
{
|
||||
public override string Name => "Default";
|
||||
public override string Description => "Default Among Us GameMode";
|
||||
public override int Id => 0;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.GameModes;
|
||||
|
||||
public class RegisterGameModeAttribute : Attribute
|
||||
{
|
||||
private static readonly HashSet<Assembly> RegisteredAssemblies = [];
|
||||
|
||||
public static void Register(Assembly assembly)
|
||||
{
|
||||
if (!RegisteredAssemblies.Add(assembly)) return;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<RegisterGameModeAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
CustomGameModeManager.RegisterGameMode(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Localization.Utilities;
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
|
||||
public abstract class AbstractGameOption
|
||||
{
|
||||
public string Title { get; }
|
||||
public StringNames StringName { get; }
|
||||
public Type AdvancedRole { get; }
|
||||
public bool Save { get; }
|
||||
public bool ShowInHideNSeek { get; init; }
|
||||
public CustomOptionGroup Group { get; set; }
|
||||
public Func<bool> Hidden { get; set; }
|
||||
public OptionBehaviour OptionBehaviour { get; protected set; }
|
||||
public void ValueChanged(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
OnValueChanged(optionBehaviour);
|
||||
CustomOptionsManager.SyncOptions();
|
||||
}
|
||||
|
||||
protected abstract void OnValueChanged(OptionBehaviour optionBehaviour);
|
||||
|
||||
protected AbstractGameOption(string title, Type roleType, bool save)
|
||||
{
|
||||
Title = title;
|
||||
StringName = CustomStringName.CreateAndRegister(Title);
|
||||
if (roleType is not null && roleType.IsAssignableTo(typeof(ICustomRole)))
|
||||
{
|
||||
AdvancedRole = roleType;
|
||||
}
|
||||
|
||||
Save = save;
|
||||
Hidden = () => false;
|
||||
CustomOptionsManager.CustomOptions.Add(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
using BepInEx.Configuration;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
|
||||
public class CustomNumberOption : AbstractGameOption
|
||||
{
|
||||
public float Value { get; private set; }
|
||||
public float Min { get; }
|
||||
public float Max { get; }
|
||||
public float Increment { get; }
|
||||
public NumberSuffixes SuffixType { get; }
|
||||
public string NumberFormat { get; }
|
||||
public float Default { get; }
|
||||
public bool ZeroInfinity { get; }
|
||||
public ConfigEntry<float> Config { get; }
|
||||
public Action<float> ChangedEvent { get; set; }
|
||||
|
||||
public CustomNumberOption(string title, float defaultValue, float min, float max, float increment, NumberSuffixes suffixType, bool zeroInfinity = false, string numberFormat = "0", Type role = null, bool save = true) : base(title, role, save)
|
||||
{
|
||||
Value = defaultValue;
|
||||
Default = defaultValue;
|
||||
Min = min;
|
||||
Max = max;
|
||||
Increment = increment;
|
||||
SuffixType = suffixType;
|
||||
NumberFormat = numberFormat;
|
||||
ZeroInfinity = zeroInfinity;
|
||||
|
||||
if (Save)
|
||||
{
|
||||
try
|
||||
{
|
||||
Config = MiraAPIPlugin.Instance.Config.Bind("Number Options", title, defaultValue);
|
||||
SetValue(Save ? Config.Value : defaultValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
CustomOptionsManager.CustomNumberOptions.Add(this);
|
||||
}
|
||||
|
||||
public void SetValue(float newValue)
|
||||
{
|
||||
newValue = Mathf.Clamp(newValue, Min, Max);
|
||||
|
||||
if (Save && (!AmongUsClient.Instance || AmongUsClient.Instance.AmHost))
|
||||
{
|
||||
try
|
||||
{
|
||||
Config.Value = newValue;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
Value = newValue;
|
||||
|
||||
var behaviour = (NumberOption)OptionBehaviour;
|
||||
if (behaviour)
|
||||
{
|
||||
behaviour.Value = Value;
|
||||
}
|
||||
|
||||
ChangedEvent?.Invoke(Value);
|
||||
}
|
||||
|
||||
public NumberOption CreateNumberOption(NumberOption original, Transform container)
|
||||
{
|
||||
var numberOption = Object.Instantiate(original, container);
|
||||
|
||||
numberOption.name = Title;
|
||||
numberOption.Title = StringName;
|
||||
numberOption.Value = Value;
|
||||
numberOption.Increment = Increment;
|
||||
numberOption.SuffixType = SuffixType;
|
||||
numberOption.FormatString = NumberFormat;
|
||||
numberOption.ValidRange = new FloatRange(Min, Max);
|
||||
numberOption.ZeroIsInfinity = ZeroInfinity;
|
||||
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
numberOption.OnEnable();
|
||||
|
||||
OptionBehaviour = numberOption;
|
||||
|
||||
return numberOption;
|
||||
}
|
||||
|
||||
protected override void OnValueChanged(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
SetValue(optionBehaviour.GetFloat());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
using MiraAPI.Roles;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
public class CustomOptionGroup
|
||||
{
|
||||
public string Title { get; }
|
||||
public Func<bool> Hidden { get; set; }
|
||||
|
||||
public GameObject Header;
|
||||
public Type AdvancedRole { get; set; }
|
||||
|
||||
public readonly List<AbstractGameOption> Options = [];
|
||||
public readonly List<CustomNumberOption> CustomNumberOptions;
|
||||
public readonly List<CustomToggleOption> CustomToggleOptions;
|
||||
public readonly List<CustomStringOption> CustomStringOptions;
|
||||
public CustomOptionGroup(string title, List<CustomNumberOption> numberOpt,
|
||||
List<CustomToggleOption> toggleOpt, List<CustomStringOption> stringOpt, Type role = null)
|
||||
{
|
||||
Title = title;
|
||||
|
||||
if (role is not null && role.IsAssignableTo(typeof(ICustomRole)))
|
||||
{
|
||||
AdvancedRole = role;
|
||||
}
|
||||
|
||||
|
||||
Hidden = () => false;
|
||||
CustomNumberOptions = numberOpt;
|
||||
CustomToggleOptions = toggleOpt;
|
||||
CustomStringOptions = stringOpt;
|
||||
|
||||
Options.AddRange(CustomNumberOptions);
|
||||
Options.AddRange(CustomToggleOptions);
|
||||
Options.AddRange(CustomStringOptions);
|
||||
|
||||
foreach (var option in Options)
|
||||
{
|
||||
option.Group = this;
|
||||
}
|
||||
|
||||
CustomOptionsManager.CustomGroups.Add(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
using MiraAPI.Networking.Options;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
|
||||
public static class CustomOptionsManager
|
||||
{
|
||||
public static readonly List<AbstractGameOption> CustomOptions = [];
|
||||
public static readonly List<CustomNumberOption> CustomNumberOptions = [];
|
||||
public static readonly List<CustomToggleOption> CustomToggleOptions = [];
|
||||
public static readonly List<CustomStringOption> CustomStringOptions = [];
|
||||
public static readonly List<CustomOptionGroup> CustomGroups = [];
|
||||
public static void SyncOptions()
|
||||
{
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var toggles = CustomToggleOptions.Select(x => x.Value).ToArray();
|
||||
var numbers = CustomNumberOptions.Select(x => x.Value).ToArray();
|
||||
var strings = CustomStringOptions.Select(x => x.IndexValue).ToArray();
|
||||
|
||||
Rpc<SyncOptionsRpc>.Instance.Send(new SyncOptionsRpc.Data(toggles, numbers, strings));
|
||||
}
|
||||
|
||||
public static void SyncOptions(int targetId)
|
||||
{
|
||||
if (!AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var toggles = CustomToggleOptions.Select(x => x.Value).ToArray();
|
||||
var numbers = CustomNumberOptions.Select(x => x.Value).ToArray();
|
||||
var strings = CustomStringOptions.Select(x => x.IndexValue).ToArray();
|
||||
|
||||
Rpc<SyncOptionsRpc>.Instance.SendTo(targetId, new SyncOptionsRpc.Data(toggles, numbers, strings));
|
||||
}
|
||||
|
||||
public static void UpdateToConfig()
|
||||
{
|
||||
foreach (var numberOpt in CustomNumberOptions)
|
||||
{
|
||||
numberOpt.SetValue(numberOpt.Save ? numberOpt.Config.Value : numberOpt.Default);
|
||||
}
|
||||
|
||||
foreach (var toggleOpt in CustomToggleOptions)
|
||||
{
|
||||
toggleOpt.SetValue(toggleOpt.Save ? toggleOpt.Config.Value : toggleOpt.Default);
|
||||
}
|
||||
|
||||
foreach (var stringOpt in CustomStringOptions)
|
||||
{
|
||||
stringOpt.SetValue(stringOpt.Save ? stringOpt.Config.Value : stringOpt.Default);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void ResetToDefault()
|
||||
{
|
||||
foreach (var numberOpt in CustomNumberOptions)
|
||||
{
|
||||
numberOpt.SetValue(numberOpt.Default);
|
||||
}
|
||||
|
||||
foreach (var stringOpt in CustomStringOptions)
|
||||
{
|
||||
stringOpt.SetValue(stringOpt.Default);
|
||||
}
|
||||
|
||||
foreach (var toggleOpt in CustomToggleOptions)
|
||||
{
|
||||
toggleOpt.SetValue(toggleOpt.Default);
|
||||
}
|
||||
|
||||
foreach (var option in CustomOptions)
|
||||
{
|
||||
if (!option.OptionBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
option.ValueChanged(option.OptionBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
public static void HandleOptionsSync(bool[] toggles, float[] numbers, int[] strings)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning("syncing options!");
|
||||
for (var i = 0; i < toggles.Length; i++)
|
||||
{
|
||||
CustomToggleOptions[i].SetValue(toggles[i]);
|
||||
}
|
||||
for (var i = 0; i < numbers.Length; i++)
|
||||
{
|
||||
CustomNumberOptions[i].SetValue(numbers[i]);
|
||||
}
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
CustomStringOptions[i].SetValue(strings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
public static class CustomOptionsTab
|
||||
{
|
||||
public static GameObject CustomTab;
|
||||
public static GameObject CustomScreen;
|
||||
public static SpriteRenderer Rend;
|
||||
|
||||
public static GameObject Initialize(GameSettingMenu __instance)
|
||||
{
|
||||
var gameBtn = __instance.transform.FindChild("Header/Tabs/GameTab").gameObject;
|
||||
var roleBtn = __instance.transform.FindChild("Header/Tabs/RoleTab").gameObject;
|
||||
|
||||
CustomScreen = CreateNewMenu(__instance);
|
||||
CustomTab = CreateCustomTab(__instance, CustomScreen, gameBtn, roleBtn);
|
||||
|
||||
Rend = CustomTab.transform.FindChild("LaunchpadBtn/Tab Background").GetComponent<SpriteRenderer>();
|
||||
Rend.enabled = false;
|
||||
|
||||
UpdateListeners(__instance, gameBtn.GetComponentInChildren<PassiveButton>(), roleBtn.GetComponentInChildren<PassiveButton>(), Rend);
|
||||
|
||||
var container = CustomScreen.transform.FindChild("GameGroup/SliderInner");
|
||||
container.DestroyChildren();
|
||||
CreateNewResetButton(__instance, container);
|
||||
|
||||
return container.gameObject;
|
||||
}
|
||||
|
||||
private static void CreateNewResetButton(GameSettingMenu __instance, Transform container)
|
||||
{
|
||||
var resetBtn = __instance.RegularGameSettings.transform.FindChild("GameGroup/SliderInner/ResetToDefault");
|
||||
var newResetBtn = Object.Instantiate(resetBtn.gameObject, container);
|
||||
newResetBtn.gameObject.name = "LaunchpadReset";
|
||||
|
||||
var tmp = newResetBtn.GetComponentInChildren<TextMeshPro>();
|
||||
tmp.text = "Reset Options";
|
||||
tmp.alignment = TextAlignmentOptions.Center;
|
||||
tmp.gameObject.transform.localPosition = new Vector3(0, 0, 0);
|
||||
|
||||
newResetBtn.GetComponent<ToggleOption>().Destroy();
|
||||
newResetBtn.transform.FindChild("CheckBox").gameObject.Destroy();
|
||||
|
||||
var toggle = newResetBtn.GetComponent<PassiveButton>();
|
||||
|
||||
toggle.OnClick.RemoveAllListeners();
|
||||
toggle.OnMouseOver.RemoveAllListeners();
|
||||
toggle.OnMouseOut.RemoveAllListeners();
|
||||
toggle.OnClick.AddListener((UnityAction)(() => { CustomOptionsManager.ResetToDefault(); }));
|
||||
toggle.OnMouseOver.AddListener((UnityAction)(() => { tmp.text = "<b>Reset Options</b>"; }));
|
||||
toggle.OnMouseOut.AddListener((UnityAction)(() => { tmp.text = "Reset Options"; }));
|
||||
}
|
||||
|
||||
private static void UpdateListeners(GameSettingMenu __instance, PassiveButton gameB, PassiveButton roleB, SpriteRenderer rend)
|
||||
{
|
||||
gameB.OnClick.RemoveAllListeners();
|
||||
gameB.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
__instance.RegularGameSettings.SetActive(true);
|
||||
__instance.RolesSettings.gameObject.SetActive(false);
|
||||
|
||||
CustomScreen.gameObject.SetActive(false);
|
||||
rend.enabled = false;
|
||||
|
||||
__instance.GameSettingsHightlight.enabled = true;
|
||||
__instance.RolesSettingsHightlight.enabled = false;
|
||||
}));
|
||||
|
||||
roleB.OnClick.RemoveAllListeners();
|
||||
roleB.OnClick.AddListener((UnityAction)(() =>
|
||||
{
|
||||
__instance.RegularGameSettings.SetActive(false);
|
||||
__instance.RolesSettings.gameObject.SetActive(true);
|
||||
|
||||
CustomScreen.gameObject.SetActive(false);
|
||||
rend.enabled = false;
|
||||
|
||||
__instance.GameSettingsHightlight.enabled = false;
|
||||
__instance.RolesSettingsHightlight.enabled = true;
|
||||
}));
|
||||
}
|
||||
|
||||
public static GameObject CreateHeader(ToggleOption toggleOpt, Transform container, string title)
|
||||
{
|
||||
var header = Object.Instantiate(toggleOpt, container);
|
||||
|
||||
header.Title = StringNames.None;
|
||||
header.TitleText.text = title;
|
||||
header.name = "Header";
|
||||
|
||||
var checkBox = header.transform.FindChild("CheckBox")?.gameObject;
|
||||
if (checkBox)
|
||||
{
|
||||
checkBox.Destroy();
|
||||
}
|
||||
|
||||
var background = header.transform.FindChild("Background")?.gameObject;
|
||||
if (background)
|
||||
{
|
||||
background.Destroy();
|
||||
}
|
||||
|
||||
header.GetComponent<OptionBehaviour>().Destroy();
|
||||
return header.gameObject;
|
||||
}
|
||||
|
||||
public static GameObject CreateCustomTab(GameSettingMenu __instance, GameObject newSettings,
|
||||
GameObject gameTab, GameObject roleTab)
|
||||
{
|
||||
var newTab = Object.Instantiate(gameTab, gameTab.transform.parent);
|
||||
newTab.name = "LaunchpadTab";
|
||||
gameTab.transform.position += new Vector3(-1, 0, 0);
|
||||
|
||||
var inside = newTab.transform.FindChild("ColorButton");
|
||||
inside.name = "LaunchpadBtn";
|
||||
|
||||
var btn = inside.GetComponentInChildren<PassiveButton>();
|
||||
|
||||
btn.OnClick.RemoveAllListeners();
|
||||
|
||||
btn.OnClick.AddListener((Action)TabAction);
|
||||
|
||||
var spriteRend = inside.GetComponentInChildren<SpriteRenderer>();
|
||||
//spriteRend.sprite = LaunchpadAssets.HackButton.LoadAsset();
|
||||
spriteRend.gameObject.transform.localScale = new Vector3(0.4f, 0.4f, 0.4f);
|
||||
|
||||
return newTab;
|
||||
|
||||
void TabAction()
|
||||
{
|
||||
__instance.RegularGameSettings.SetActive(false);
|
||||
__instance.RolesSettings.gameObject.SetActive(false);
|
||||
newSettings.gameObject.SetActive(true);
|
||||
|
||||
var rend = inside.transform.FindChild("Tab Background").GetComponent<SpriteRenderer>();
|
||||
rend.enabled = true;
|
||||
|
||||
gameTab.transform.FindChild("ColorButton/Tab Background").gameObject.GetComponent<SpriteRenderer>().enabled = false;
|
||||
roleTab.transform.FindChild("Hat Button/Tab Background").gameObject.GetComponent<SpriteRenderer>().enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static GameObject CreateNewMenu(GameSettingMenu __instance)
|
||||
{
|
||||
var gameSettings = __instance.RegularGameSettings;
|
||||
var newSettings = Object.Instantiate(gameSettings, gameSettings.transform.parent);
|
||||
newSettings.name = "Launchpad Settings";
|
||||
newSettings.SetActive(false);
|
||||
|
||||
var launchpadGroup = newSettings.transform.FindChild("GameGroup").gameObject;
|
||||
var text = launchpadGroup.transform.FindChild("Text").gameObject.GetComponent<TextMeshPro>();
|
||||
text.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.text = "Launchpad Settings";
|
||||
|
||||
return newSettings;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
using BepInEx.Configuration;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
|
||||
public class CustomStringOption : AbstractGameOption
|
||||
{
|
||||
public int IndexValue { get; private set; }
|
||||
public string Value => Options[IndexValue];
|
||||
public int Default { get; }
|
||||
public string[] Options { get; private set; }
|
||||
public ConfigEntry<int> Config { get; }
|
||||
public Action<int, string> ChangedEvent { get; init; }
|
||||
public CustomStringOption(string title, int defaultValue, string[] options, Type role = null, bool save = true) : base(title, role, save)
|
||||
{
|
||||
IndexValue = defaultValue;
|
||||
Options = options;
|
||||
Default = defaultValue;
|
||||
|
||||
CustomOptionsManager.CustomStringOptions.Add(this);
|
||||
if (Save)
|
||||
{
|
||||
try
|
||||
{
|
||||
Config = MiraAPIPlugin.Instance.Config.Bind("String Options", title, defaultValue);
|
||||
SetValue(Save ? Config.Value : defaultValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void SetValue(int newValue)
|
||||
{
|
||||
if (Save && (!AmongUsClient.Instance || AmongUsClient.Instance.AmHost))
|
||||
{
|
||||
try
|
||||
{
|
||||
Config.Value = newValue;
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
var oldValue = IndexValue;
|
||||
IndexValue = newValue;
|
||||
|
||||
var behaviour = (StringOption)OptionBehaviour;
|
||||
if (behaviour)
|
||||
{
|
||||
behaviour.Value = newValue;
|
||||
}
|
||||
|
||||
if (oldValue != newValue)
|
||||
{
|
||||
ChangedEvent?.Invoke(newValue, Value);
|
||||
}
|
||||
}
|
||||
|
||||
public void SetValue(string newValue) => SetValue(Options.ToList().IndexOf(newValue));
|
||||
|
||||
protected override void OnValueChanged(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
SetValue(optionBehaviour.GetInt());
|
||||
}
|
||||
|
||||
public StringOption CreateStringOption(StringOption original, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(original, container);
|
||||
|
||||
stringOption.name = Title;
|
||||
stringOption.Title = StringName;
|
||||
stringOption.Value = Options.ToList().IndexOf(Value);
|
||||
stringOption.Values = Options.Select(CustomStringName.CreateAndRegister).ToArray();
|
||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
stringOption.OnEnable();
|
||||
|
||||
OptionBehaviour = stringOption;
|
||||
|
||||
return stringOption;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
using BepInEx.Configuration;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.API.GameOptions;
|
||||
|
||||
public class CustomToggleOption : AbstractGameOption
|
||||
{
|
||||
public bool Value { get; private set; }
|
||||
public bool Default { get; }
|
||||
public ConfigEntry<bool> Config { get; }
|
||||
public Action<bool> ChangedEvent { get; init; }
|
||||
public CustomToggleOption(string title, bool defaultValue, Type role = null, bool save = true) : base(title, role, save)
|
||||
{
|
||||
Default = defaultValue;
|
||||
if (Save)
|
||||
{
|
||||
try
|
||||
{
|
||||
Config = MiraAPIPlugin.Instance.Config.Bind("Toggle Options", title, defaultValue);
|
||||
SetValue(Save ? Config.Value : defaultValue);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
CustomOptionsManager.CustomToggleOptions.Add(this);
|
||||
}
|
||||
|
||||
public void SetValue(bool newValue)
|
||||
{
|
||||
if (Save && (!AmongUsClient.Instance || AmongUsClient.Instance.AmHost))
|
||||
{
|
||||
try
|
||||
{
|
||||
Config.Value = newValue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
var oldValue = Value;
|
||||
Value = newValue;
|
||||
|
||||
var behaviour = (ToggleOption)OptionBehaviour;
|
||||
if (behaviour)
|
||||
{
|
||||
behaviour.CheckMark.enabled = newValue;
|
||||
}
|
||||
|
||||
if (newValue != oldValue)
|
||||
{
|
||||
ChangedEvent?.Invoke(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnValueChanged(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
SetValue(optionBehaviour.GetBool());
|
||||
}
|
||||
|
||||
public ToggleOption CreateToggleOption(ToggleOption original, Transform container)
|
||||
{
|
||||
var toggleOption = Object.Instantiate(original, container);
|
||||
|
||||
toggleOption.name = Title;
|
||||
toggleOption.Title = StringName;
|
||||
toggleOption.CheckMark.enabled = Value;
|
||||
toggleOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
toggleOption.OnEnable();
|
||||
OptionBehaviour = toggleOption;
|
||||
return toggleOption;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
public abstract class CustomActionButton
|
||||
{
|
||||
public abstract string Name { get; }
|
||||
|
||||
public abstract float Cooldown { get; }
|
||||
|
||||
public abstract float EffectDuration { get; }
|
||||
|
||||
public abstract int MaxUses { get; }
|
||||
|
||||
public abstract LoadableAsset<Sprite> Sprite { get; }
|
||||
|
||||
public bool HasEffect => EffectDuration > 0;
|
||||
|
||||
public bool LimitedUses => MaxUses > 0;
|
||||
|
||||
protected bool EffectActive;
|
||||
|
||||
protected float Timer;
|
||||
|
||||
protected int UsesLeft;
|
||||
|
||||
protected ActionButton Button;
|
||||
|
||||
protected DeadBody DeadBodyTarget;
|
||||
|
||||
public void CreateButton(Transform parent)
|
||||
{
|
||||
if (Button)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UsesLeft = MaxUses;
|
||||
Timer = Cooldown;
|
||||
EffectActive = false;
|
||||
|
||||
Button = Object.Instantiate(HudManager.Instance.AbilityButton, parent);
|
||||
Button.name = Name + "Button";
|
||||
Button.OverrideText(Name.ToUpper());
|
||||
|
||||
Button.graphic.sprite = Sprite.LoadAsset();
|
||||
|
||||
Button.SetUsesRemaining(MaxUses);
|
||||
if (MaxUses <= 0)
|
||||
{
|
||||
Button.SetInfiniteUses();
|
||||
}
|
||||
|
||||
var pb = Button.GetComponent<PassiveButton>();
|
||||
pb.OnClick.RemoveAllListeners();
|
||||
pb.OnClick.AddListener((UnityAction)ClickHandler);
|
||||
}
|
||||
|
||||
public void ResetCooldown()
|
||||
{
|
||||
Timer = Cooldown;
|
||||
if (EffectActive)
|
||||
{
|
||||
OnEffectEnd();
|
||||
}
|
||||
|
||||
EffectActive = false;
|
||||
}
|
||||
|
||||
public void OverrideSprite(Sprite sprite)
|
||||
{
|
||||
Button.graphic.sprite = sprite;
|
||||
}
|
||||
|
||||
public void OverrideName(string name)
|
||||
{
|
||||
Button.OverrideText(name);
|
||||
}
|
||||
|
||||
protected virtual void FixedUpdate(PlayerControl playerControl) { }
|
||||
|
||||
protected abstract void OnClick();
|
||||
|
||||
public abstract bool Enabled(RoleBehaviour role);
|
||||
|
||||
public virtual void OnEffectEnd() { }
|
||||
|
||||
private bool CanUseHandler()
|
||||
{
|
||||
return Timer <= 0 && !EffectActive && (!LimitedUses || UsesLeft > 0) && CanUse();
|
||||
}
|
||||
|
||||
public virtual bool CanUse()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public virtual void SetActive(bool visible, RoleBehaviour role)
|
||||
{
|
||||
Button.ToggleVisible(visible && Enabled(role));
|
||||
}
|
||||
|
||||
private void ClickHandler()
|
||||
{
|
||||
if (!CanUseHandler())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (LimitedUses)
|
||||
{
|
||||
UsesLeft--;
|
||||
Button.SetUsesRemaining(UsesLeft);
|
||||
}
|
||||
|
||||
OnClick();
|
||||
Button.SetDisabled();
|
||||
if (HasEffect)
|
||||
{
|
||||
EffectActive = true;
|
||||
Timer = EffectDuration;
|
||||
}
|
||||
else
|
||||
{
|
||||
Timer = Cooldown;
|
||||
}
|
||||
}
|
||||
|
||||
public void FixedUpdateHandler(PlayerControl playerControl)
|
||||
{
|
||||
if (Timer >= 0)
|
||||
{
|
||||
Timer -= Time.deltaTime;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (HasEffect && EffectActive)
|
||||
{
|
||||
EffectEndHandler();
|
||||
}
|
||||
}
|
||||
|
||||
if (CanUseHandler())
|
||||
{
|
||||
Button.SetEnabled();
|
||||
}
|
||||
else
|
||||
{
|
||||
Button.SetDisabled();
|
||||
}
|
||||
Button.SetCoolDown(Timer, EffectActive ? EffectDuration : Cooldown);
|
||||
|
||||
playerControl.UpdateBodies(playerControl.Data.Role.TeamColor, ref DeadBodyTarget);
|
||||
FixedUpdate(playerControl);
|
||||
}
|
||||
|
||||
private void EffectEndHandler()
|
||||
{
|
||||
EffectActive = false;
|
||||
Timer = Cooldown;
|
||||
OnEffectEnd();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
public static class CustomButtonManager
|
||||
{
|
||||
public static readonly List<CustomActionButton> CustomButtons = [];
|
||||
|
||||
|
||||
public static void RegisterButton(Type buttonType)
|
||||
{
|
||||
if (!typeof(CustomActionButton).IsAssignableFrom(buttonType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var button = (CustomActionButton)Activator.CreateInstance(buttonType);
|
||||
CustomButtons.Add(button);
|
||||
typeof(CustomButtonSingleton<>).MakeGenericType(buttonType)
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
.SetValue(null, button);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
public class CustomButtonSingleton<T> where T : CustomActionButton
|
||||
{
|
||||
private static T? _instance;
|
||||
|
||||
public static T Instance
|
||||
{
|
||||
get => _instance ??= CustomButtonManager.CustomButtons.OfType<T>().Single();
|
||||
set
|
||||
{
|
||||
if (_instance == value) return;
|
||||
if (_instance != null) throw new InvalidOperationException($"Instance for {typeof(T)} is already set");
|
||||
_instance = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.Hud;
|
||||
|
||||
public class RegisterButtonAttribute : Attribute
|
||||
{
|
||||
private static readonly HashSet<Assembly> RegisteredAssemblies = [];
|
||||
|
||||
public static void Register(Assembly assembly)
|
||||
{
|
||||
if (!RegisteredAssemblies.Add(assembly)) return;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<RegisterButtonAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
CustomButtonManager.RegisterButton(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
using BepInEx;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameModes;
|
||||
using MiraAPI.Hud;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor;
|
||||
using Reactor.Networking;
|
||||
using Reactor.Networking.Attributes;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI;
|
||||
|
||||
@@ -14,9 +18,15 @@ namespace MiraAPI;
|
||||
public partial class MiraAPIPlugin : BasePlugin
|
||||
{
|
||||
public Harmony Harmony { get; } = new(Id);
|
||||
public static MiraAPIPlugin Instance { get; private set; }
|
||||
|
||||
public override void Load()
|
||||
{
|
||||
Instance = this;
|
||||
Harmony.PatchAll();
|
||||
|
||||
RegisterGameModeAttribute.Register(Assembly.GetExecutingAssembly());
|
||||
RegisterCustomRoleAttribute.Register(Assembly.GetExecutingAssembly());
|
||||
RegisterButtonAttribute.Register(Assembly.GetExecutingAssembly());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MiraAPI.Networking
|
||||
{
|
||||
public enum MiraRpc : uint
|
||||
{
|
||||
SyncGameOptions,
|
||||
SyncRoleOptions
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
using Hazel;
|
||||
using MiraAPI.API.GameOptions;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
|
||||
namespace MiraAPI.Networking.Options;
|
||||
|
||||
// METHOD RPC DOESNT WORK WITH THE ARRAYS AND STUFF SO THIS IS HOW WE WILL DO IT FOR NOW
|
||||
[RegisterCustomRpc((uint)MiraRpc.SyncGameOptions)]
|
||||
public class SyncOptionsRpc(MiraAPIPlugin plugin, uint id) : PlayerCustomRpc<MiraAPIPlugin, SyncOptionsRpc.Data>(plugin, id)
|
||||
{
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public readonly struct Data
|
||||
{
|
||||
public readonly bool[] Toggles;
|
||||
public readonly float[] Numbers;
|
||||
public readonly int[] StringIDs;
|
||||
|
||||
public Data(bool[] toggles, float[] numbers, int[] stringIDs)
|
||||
{
|
||||
Toggles = toggles;
|
||||
Numbers = numbers;
|
||||
StringIDs = stringIDs;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(MessageWriter writer, Data data)
|
||||
{
|
||||
writer.WritePacked((uint)data.Toggles.Length);
|
||||
foreach (var t in data.Toggles)
|
||||
{
|
||||
writer.Write(t);
|
||||
}
|
||||
|
||||
writer.WritePacked((uint)data.Numbers.Length);
|
||||
foreach (var n in data.Numbers)
|
||||
{
|
||||
writer.Write(n);
|
||||
}
|
||||
|
||||
writer.WritePacked((uint)data.StringIDs.Length);
|
||||
foreach (var n in data.StringIDs)
|
||||
{
|
||||
writer.WritePacked(n);
|
||||
}
|
||||
}
|
||||
|
||||
public override Data Read(MessageReader reader)
|
||||
{
|
||||
var toggles = new bool[reader.ReadPackedUInt32()];
|
||||
for (var i = 0; i < toggles.Length; i++)
|
||||
{
|
||||
toggles[i] = reader.ReadBoolean();
|
||||
}
|
||||
|
||||
var numbers = new float[reader.ReadPackedUInt32()];
|
||||
for (var i = 0; i < numbers.Length; i++)
|
||||
{
|
||||
numbers[i] = reader.ReadSingle();
|
||||
}
|
||||
|
||||
var strings = new int[reader.ReadPackedUInt32()];
|
||||
for (var i = 0; i < strings.Length; i++)
|
||||
{
|
||||
strings[i] = reader.ReadPackedInt32();
|
||||
}
|
||||
|
||||
|
||||
return new Data(toggles, numbers, strings);
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl playerControl, Data data)
|
||||
{
|
||||
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CustomOptionsManager.HandleOptionsSync(data.Toggles, data.Numbers, data.StringIDs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using Hazel;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Networking.Options;
|
||||
|
||||
[RegisterCustomRpc((uint)MiraRpc.SyncRoleOptions)]
|
||||
public class SyncRoleOptionsRpc(MiraAPIPlugin plugin, uint id) : PlayerCustomRpc<MiraAPIPlugin, SyncRoleOptionsRpc.Data>(plugin, id)
|
||||
{
|
||||
|
||||
public struct Data(ushort roleId, int number, int chance)
|
||||
{
|
||||
public readonly ushort RoleId = roleId;
|
||||
public readonly int Number = number;
|
||||
public readonly int Chance = chance;
|
||||
}
|
||||
|
||||
public override RpcLocalHandling LocalHandling => RpcLocalHandling.None;
|
||||
|
||||
public override void Write(MessageWriter writer, Data data)
|
||||
{
|
||||
writer.Write(data.RoleId);
|
||||
writer.WritePacked(data.Number);
|
||||
writer.WritePacked(data.Chance);
|
||||
}
|
||||
|
||||
public override Data Read(MessageReader reader)
|
||||
{
|
||||
return new Data(reader.ReadUInt16(), reader.ReadPackedInt32(), reader.ReadPackedInt32());
|
||||
}
|
||||
|
||||
public override void Handle(PlayerControl playerControl, Data data)
|
||||
{
|
||||
if (AmongUsClient.Instance.HostId != playerControl.OwnerId)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CustomRoleManager.CustomRoles.TryGetValue(data.RoleId, out var roleBehaviour) ||
|
||||
!CustomRoleManager.GetCustomRoleBehaviour(roleBehaviour.Role, out var role)) return;
|
||||
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(role.NumConfigDefinition, out var numEntry);
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(role.ChanceConfigDefinition, out var chanceEntry);
|
||||
|
||||
try
|
||||
{
|
||||
numEntry.Value = data.Number;
|
||||
chanceEntry.Value = data.Chance;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Warning(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.API.GameOptions;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
[HarmonyPatch(typeof(GameOptionsMenu))]
|
||||
public static class GameOptionsMenuPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Change the OnValueChanged action for all custom game options
|
||||
/// </summary>
|
||||
[HarmonyPostfix, HarmonyPatch("Start")]
|
||||
public static void StartPostfix(GameOptionsMenu __instance)
|
||||
{
|
||||
foreach (var customOption in CustomOptionsManager.CustomOptions)
|
||||
{
|
||||
if (customOption.AdvancedRole is not null || !customOption.OptionBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
customOption.OptionBehaviour.OnValueChanged = (Action<OptionBehaviour>)customOption.ValueChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private static GameSettingMenu menu;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Set the position and offset of all custom game options
|
||||
/// </summary>
|
||||
[HarmonyPostfix, HarmonyPatch("Update")]
|
||||
public static void UpdatePostfix(GameOptionsMenu __instance)
|
||||
{
|
||||
if (!menu)
|
||||
{
|
||||
menu = Object.FindObjectsOfType<GameSettingMenu>().First();
|
||||
}
|
||||
|
||||
if (menu.RegularGameSettings.active || menu.RolesSettings.gameObject.active || menu.HideNSeekSettings.active)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var startOffset = 2.15f;
|
||||
__instance.GetComponentInParent<Scroller>().ContentYBounds.max = startOffset + __instance.Children.Count * 0.5f;
|
||||
|
||||
foreach (var option in CustomOptionsManager.CustomOptions.Where(option => option.Group == null))
|
||||
{
|
||||
if (!option.OptionBehaviour) continue;
|
||||
|
||||
option.OptionBehaviour.enabled = !option.Hidden();
|
||||
option.OptionBehaviour.gameObject.SetActive(!option.Hidden());
|
||||
|
||||
if (!option.Hidden())
|
||||
{
|
||||
startOffset -= 0.55f;
|
||||
}
|
||||
|
||||
var transform = option.OptionBehaviour.transform;
|
||||
var optionPosition = transform.localPosition;
|
||||
transform.localPosition = new Vector3(optionPosition.x, startOffset, optionPosition.z);
|
||||
}
|
||||
|
||||
foreach (var group in CustomOptionsManager.CustomGroups.Where(group => group.AdvancedRole == null))
|
||||
{
|
||||
if (group.Header == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
group.Header.SetActive(!group.Hidden());
|
||||
|
||||
if (!group.Hidden())
|
||||
{
|
||||
startOffset -= 0.5f;
|
||||
}
|
||||
|
||||
var position = group.Header.transform.localPosition;
|
||||
group.Header.transform.localPosition = new Vector3(position.x, startOffset, position.z);
|
||||
|
||||
foreach (var option in group.Options)
|
||||
{
|
||||
if (!option.OptionBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var enabled = !group.Hidden() && !option.Hidden();
|
||||
|
||||
option.OptionBehaviour.enabled = enabled;
|
||||
option.OptionBehaviour.gameObject.SetActive(enabled);
|
||||
|
||||
if (!group.Hidden() && !option.Hidden())
|
||||
{
|
||||
startOffset -= 0.5f;
|
||||
}
|
||||
|
||||
var transform = option.OptionBehaviour.transform;
|
||||
var optionPosition = transform.localPosition;
|
||||
transform.localPosition = new Vector3(optionPosition.x, startOffset, optionPosition.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.API.GameOptions;
|
||||
using MiraAPI.GameModes;
|
||||
using Reactor.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
[HarmonyPatch(typeof(GameSettingMenu))]
|
||||
public static class GameSettingsMenuPatches
|
||||
{
|
||||
private static GameObject _gameBtn;
|
||||
private static GameObject _roleBtn;
|
||||
|
||||
[HarmonyPrefix, HarmonyPatch("Start")]
|
||||
public static void StartPrefix(GameSettingMenu __instance)
|
||||
{
|
||||
if (CustomOptionsTab.CustomTab)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.Tabs.transform.position += new Vector3(0.5f, 0, 0);
|
||||
_gameBtn = __instance.transform.FindChild("Header/Tabs/GameTab").gameObject;
|
||||
_roleBtn = __instance.transform.FindChild("Header/Tabs/RoleTab").gameObject;
|
||||
|
||||
var numberOpt = __instance.RegularGameSettings.GetComponentInChildren<NumberOption>();
|
||||
var toggleOpt = Object.FindObjectOfType<ToggleOption>();
|
||||
var stringOpt = __instance.RegularGameSettings.GetComponentInChildren<StringOption>();
|
||||
var container = GameManager.Instance.IsNormal() ? CustomOptionsTab.Initialize(__instance).transform : __instance.HideAndSeekScroller.Inner;
|
||||
|
||||
foreach (var group in CustomOptionsManager.CustomGroups.Where(group => group.AdvancedRole == null))
|
||||
{
|
||||
if (GameManager.Instance.IsNormal())
|
||||
{
|
||||
group.Header = CustomOptionsTab.CreateHeader(toggleOpt, container, group.Title);
|
||||
CreateOptionsFor(__instance, toggleOpt, numberOpt, stringOpt, container,
|
||||
group.CustomToggleOptions, group.CustomNumberOptions, group.CustomStringOptions);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!group.Options.Any(x => x.ShowInHideNSeek))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
group.Header = CustomOptionsTab.CreateHeader(toggleOpt, container, group.Title);
|
||||
__instance.AllHideAndSeekItems = __instance.AllHideAndSeekItems.Append(group.Header.transform).ToArray();
|
||||
|
||||
CreateOptionsFor(__instance, toggleOpt, numberOpt, stringOpt, container,
|
||||
group.CustomToggleOptions, group.CustomNumberOptions, group.CustomStringOptions);
|
||||
}
|
||||
|
||||
CreateOptionsFor(__instance, toggleOpt, numberOpt, stringOpt, container,
|
||||
CustomOptionsManager.CustomToggleOptions.Where(option => option.Group == null),
|
||||
CustomOptionsManager.CustomNumberOptions.Where(option => option.Group == null),
|
||||
CustomOptionsManager.CustomStringOptions.Where(option => option.Group == null));
|
||||
|
||||
if (!numberOpt || !toggleOpt || !stringOpt)
|
||||
{
|
||||
Logger<MiraAPIPlugin>.Error("OPTION PREFABS MISSING");
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix, HarmonyPatch("Update")]
|
||||
public static void UpdatePatch(GameSettingMenu __instance)
|
||||
{
|
||||
if (CustomGameModeManager.ActiveMode == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_gameBtn.SetActive(CustomGameModeManager.ActiveMode.CanAccessSettingsTab());
|
||||
_roleBtn.SetActive(CustomGameModeManager.ActiveMode.CanAccessRolesTab());
|
||||
|
||||
if (!CustomGameModeManager.ActiveMode.CanAccessSettingsTab())
|
||||
{
|
||||
__instance.RegularGameSettings.SetActive(false);
|
||||
__instance.RolesSettings.gameObject.SetActive(false);
|
||||
|
||||
CustomOptionsTab.CustomScreen.gameObject.SetActive(true);
|
||||
CustomOptionsTab.Rend.enabled = true;
|
||||
|
||||
__instance.GameSettingsHightlight.enabled = false;
|
||||
__instance.RolesSettingsHightlight.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void CreateOptionsFor(GameSettingMenu __instance, ToggleOption togglePrefab, NumberOption numberPrefab, StringOption stringPrefab, Transform container,
|
||||
IEnumerable<CustomToggleOption> toggles, IEnumerable<CustomNumberOption> numbers, IEnumerable<CustomStringOption> strings)
|
||||
{
|
||||
foreach (var customToggleOption in toggles)
|
||||
{
|
||||
if (customToggleOption.AdvancedRole is not null || customToggleOption.OptionBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ToggleOption toggleOption;
|
||||
|
||||
if (GameManager.Instance.IsNormal())
|
||||
{
|
||||
toggleOption = customToggleOption.CreateToggleOption(togglePrefab, container);
|
||||
__instance.AllItems = __instance.AllItems.AddItem(toggleOption.transform).ToArray();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!customToggleOption.ShowInHideNSeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
toggleOption = customToggleOption.CreateToggleOption(togglePrefab, container);
|
||||
__instance.AllHideAndSeekItems = __instance.AllHideAndSeekItems.AddItem(toggleOption.transform).ToArray();
|
||||
|
||||
}
|
||||
|
||||
foreach (var customNumberOption in numbers)
|
||||
{
|
||||
if (customNumberOption.AdvancedRole is not null || customNumberOption.OptionBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
NumberOption numberOption;
|
||||
|
||||
if (GameManager.Instance.IsNormal())
|
||||
{
|
||||
numberOption = customNumberOption.CreateNumberOption(numberPrefab, container);
|
||||
__instance.AllItems = __instance.AllItems.AddItem(numberOption.transform).ToArray();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!customNumberOption.ShowInHideNSeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
numberOption = customNumberOption.CreateNumberOption(numberPrefab, container);
|
||||
__instance.AllHideAndSeekItems = __instance.AllHideAndSeekItems.AddItem(numberOption.transform).ToArray();
|
||||
}
|
||||
|
||||
foreach (var customStringOption in strings)
|
||||
{
|
||||
if (customStringOption.AdvancedRole is not null || customStringOption.OptionBehaviour)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
StringOption stringOption;
|
||||
|
||||
if (GameManager.Instance.IsNormal())
|
||||
{
|
||||
stringOption = customStringOption.CreateStringOption(stringPrefab, container);
|
||||
__instance.AllItems = __instance.AllItems.AddItem(stringOption.transform).ToArray();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!customStringOption.ShowInHideNSeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
stringOption = customStringOption.CreateStringOption(stringPrefab, container);
|
||||
__instance.AllHideAndSeekItems = __instance.AllHideAndSeekItems.AddItem(stringOption.transform).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,150 @@
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.API.GameOptions;
|
||||
using MiraAPI.GameModes;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace MiraAPI.Patches.Options;
|
||||
|
||||
[HarmonyPatch(typeof(IGameOptionsExtensions), "ToHudString")]
|
||||
public static class ToHudStringPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Show custom Launchpad options or vanilla game options
|
||||
/// </summary>
|
||||
public static bool ShowCustom = false;
|
||||
|
||||
/// <summary>
|
||||
/// Generic method to add options
|
||||
/// </summary>
|
||||
public static void AddOptions(StringBuilder sb,
|
||||
IEnumerable<CustomNumberOption> numberOptions, IEnumerable<CustomStringOption> stringOptions, IEnumerable<CustomToggleOption> toggleOptions)
|
||||
{
|
||||
foreach (var numberOption in numberOptions.Where(x => !x.Hidden()))
|
||||
{
|
||||
if (GameManager.Instance.IsHideAndSeek() && !numberOption.ShowInHideNSeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine(numberOption.Title + ": " + numberOption.Value + Helpers.GetSuffix(numberOption.SuffixType));
|
||||
}
|
||||
|
||||
foreach (var toggleOption in toggleOptions.Where(x => !x.Hidden()))
|
||||
{
|
||||
if (GameManager.Instance.IsHideAndSeek() && !toggleOption.ShowInHideNSeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine(toggleOption.Title + ": " + (toggleOption.Value ? "On" : "Off"));
|
||||
}
|
||||
|
||||
foreach (var stringOption in stringOptions.Where(x => !x.Hidden()))
|
||||
{
|
||||
if (GameManager.Instance.IsHideAndSeek() && !stringOption.ShowInHideNSeek)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine(stringOption.Title + ": " + stringOption.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Prefix()
|
||||
{
|
||||
if (GameManager.Instance is null || GameManager.Instance.IsHideAndSeek())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var role in CustomRoleManager.CustomRoles.Values)
|
||||
{
|
||||
var customRole = role as ICustomRole;
|
||||
if (customRole.IsGhostRole)
|
||||
{
|
||||
role.Role = RoleTypes.CrewmateGhost;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the HudOptions on the left of the screen if player is using Launchpad options
|
||||
/// </summary>
|
||||
public static void Postfix(IGameOptions __instance, ref string __result)
|
||||
{
|
||||
if (GameManager.Instance is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var role in CustomRoleManager.CustomRoles.Values)
|
||||
{
|
||||
var customRole = role as ICustomRole;
|
||||
if (customRole.IsGhostRole)
|
||||
{
|
||||
role.Role = (RoleTypes)customRole.RoleId;
|
||||
}
|
||||
}
|
||||
|
||||
if (ShowCustom || !CustomGameModeManager.ActiveMode.CanAccessSettingsTab())
|
||||
{
|
||||
var sb = new StringBuilder("<size=180%><b>Launchpad Options:</b></size>\n<size=130%>");
|
||||
var groupsWithRoles = CustomOptionsManager.CustomGroups.Where(group => group.AdvancedRole != null);
|
||||
var groupsWithoutRoles = CustomOptionsManager.CustomGroups.Where(group => group.AdvancedRole == null);
|
||||
|
||||
var suffix = CustomGameModeManager.ActiveMode.CanAccessSettingsTab() ? "\nPress <b>Tab</b> to view Normal Options" :
|
||||
$"\n<b>You can not access Normal Options on {Cus tomGameModeManager.ActiveMode.Name} mode.</b>";
|
||||
|
||||
AddOptions(sb,
|
||||
CustomOptionsManager.CustomNumberOptions.Where(option => option.Group == null && !option.Hidden()),
|
||||
CustomOptionsManager.CustomStringOptions.Where(option => option.Group == null && !option.Hidden()),
|
||||
CustomOptionsManager.CustomToggleOptions.Where(option => option.Group == null && !option.Hidden())
|
||||
);
|
||||
|
||||
foreach (var group in groupsWithoutRoles)
|
||||
{
|
||||
if (group.Hidden() || (GameManager.Instance.IsHideAndSeek() && !group.Options.Any(x => x.ShowInHideNSeek)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine($"\n<size=160%><b>{group.Title}</b></size>");
|
||||
AddOptions(sb, group.CustomNumberOptions, group.CustomStringOptions, group.CustomToggleOptions);
|
||||
}
|
||||
|
||||
if (GameManager.Instance.IsHideAndSeek())
|
||||
{
|
||||
__result = sb + suffix;
|
||||
return;
|
||||
}
|
||||
|
||||
var customOptionGroups = groupsWithRoles as CustomOptionGroup[] ?? groupsWithRoles.ToArray();
|
||||
if (customOptionGroups.Any() && CustomGameModeManager.ActiveMode.CanAccessRolesTab())
|
||||
{
|
||||
sb.AppendLine("\n<size=160%><b>Roles</b></size>");
|
||||
|
||||
foreach (var group in customOptionGroups)
|
||||
{
|
||||
if (group.Hidden())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.AppendLine($"<size=140%><b>{group.Title}</b></size><size=120%>");
|
||||
AddOptions(sb, group.CustomNumberOptions, group.CustomStringOptions, group.CustomToggleOptions);
|
||||
sb.Append("</size>\n");
|
||||
}
|
||||
}
|
||||
|
||||
__result = sb + suffix;
|
||||
return;
|
||||
}
|
||||
|
||||
__result = "<size=160%><b>Normal Options:</b></size>\n<size=130%>" + __result + "\nPress <b>Tab</b> to view Launchpad Options</size>";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using HarmonyLib;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(MainMenuManager))]
|
||||
public static class GameStartupPatch
|
||||
{
|
||||
private static bool _runOnce;
|
||||
|
||||
/// <summary>
|
||||
/// This is used for registering roles when the game opens, might be a janky solution but it works
|
||||
/// </summary>
|
||||
[HarmonyPostfix, HarmonyPatch("Start")]
|
||||
public static void StartPostfix()
|
||||
{
|
||||
if (_runOnce)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_runOnce = true;
|
||||
CustomRoleManager.RegisterInRoleManager();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Set nametag color depending on option
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(PlayerNameColor))]
|
||||
public static class NameTagPatch
|
||||
{
|
||||
[HarmonyPrefix, HarmonyPatch("Get", typeof(RoleBehaviour))]
|
||||
public static bool GetPatch([HarmonyArgument(0)] RoleBehaviour otherPlayerRole, ref Color __result)
|
||||
{
|
||||
if (PlayerControl.LocalPlayer.Data.IsDead)
|
||||
{
|
||||
__result = otherPlayerRole.NameColor;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PlayerControl.LocalPlayer.Data.Role.IsImpostor && otherPlayerRole.IsImpostor)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (PlayerControl.LocalPlayer == null || PlayerControl.LocalPlayer.Data == null || PlayerControl.LocalPlayer.Data.Role == null || otherPlayerRole == null)
|
||||
{
|
||||
__result = Color.white;
|
||||
}
|
||||
|
||||
__result = PlayerControl.LocalPlayer.Data?.Role == otherPlayerRole ? otherPlayerRole.NameColor : Color.white;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(RoleBehaviour))]
|
||||
public static class RoleBehaviourPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Update TeamColor text for Launchpad roles
|
||||
/// </summary>
|
||||
[HarmonyPrefix, HarmonyPatch("TeamColor", MethodType.Getter)]
|
||||
public static bool PrefixTeamColorGetter(RoleBehaviour __instance, ref Color __result)
|
||||
{
|
||||
if (__instance is not ICustomRole behaviour)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
__result = behaviour.RoleColor;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(RoleManager))]
|
||||
public static class RoleManagerPatches
|
||||
{
|
||||
[HarmonyPrefix, HarmonyPatch("AssignRoleOnDeath")]
|
||||
public static bool AssignRoleOnDeath(RoleManager __instance, [HarmonyArgument(0)] PlayerControl plr)
|
||||
{
|
||||
if (plr == null || !plr.Data.IsDead)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plr.Data.Role is ICustomRole role)
|
||||
{
|
||||
Debug.Log(role.RoleName);
|
||||
if (role.GhostRole != RoleTypes.CrewmateGhost && role.GhostRole != RoleTypes.ImpostorGhost)
|
||||
{
|
||||
plr.RpcSetRole(role.GhostRole);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Roles;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(RoleOptionSetting))]
|
||||
public static class RoleOptionSettingPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Update the role details screen in role settings for Launchpad roles
|
||||
/// </summary>
|
||||
[HarmonyPrefix, HarmonyPatch("ShowRoleDetails")]
|
||||
public static bool ShowRoleDetailsPrefix(RoleOptionSetting __instance)
|
||||
{
|
||||
if (__instance.Role is ICustomRole customRole)
|
||||
{
|
||||
GameSettingMenu.Instance.RoleName.text = __instance.Role.NiceName;
|
||||
GameSettingMenu.Instance.RoleBlurb.text = __instance.Role.BlurbLong;
|
||||
GameSettingMenu.Instance.RoleIcon.sprite = customRole.Icon.LoadAsset();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(RoleOptionsCollectionV07))]
|
||||
public static class RoleOptionsCollectionPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// Set the role chance for custom Launchpad roles based on config
|
||||
/// </summary>
|
||||
[HarmonyPrefix, HarmonyPatch("GetChancePerGame")]
|
||||
public static bool GetChancePrefix([HarmonyArgument(0)] RoleTypes roleType, ref int __result)
|
||||
{
|
||||
if (CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole))
|
||||
{
|
||||
if (customRole.HideSettings)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(customRole.ChanceConfigDefinition, out var entry);
|
||||
__result = entry.Value;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set the amount for custom Launchpad roles based on config
|
||||
/// </summary>
|
||||
[HarmonyPrefix, HarmonyPatch("GetNumPerGame")]
|
||||
public static bool GetNumPrefix([HarmonyArgument(0)] RoleTypes roleType, ref int __result)
|
||||
{
|
||||
if (CustomRoleManager.GetCustomRoleBehaviour(roleType, out var customRole))
|
||||
{
|
||||
if (customRole.HideSettings)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(customRole.NumConfigDefinition, out var entry);
|
||||
__result = entry.Value;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppSystem;
|
||||
using MiraAPI.API.GameOptions;
|
||||
using MiraAPI.Networking.Options;
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(RolesSettingsMenu))]
|
||||
public static class RolesSettingsMenuPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Create role buttons and advanced role settings menu for every custom Launchpad role
|
||||
/// </summary>
|
||||
[HarmonyPrefix, HarmonyPatch("OnEnable")]
|
||||
public static void OnEnablePrefix(RolesSettingsMenu __instance)
|
||||
{
|
||||
var tabPrefab = __instance.AllAdvancedSettingTabs.ToArray()[1].Tab;
|
||||
foreach (var (key, role) in CustomRoleManager.CustomRoles)
|
||||
{
|
||||
if (__instance.AllAdvancedSettingTabs.ToArray().Any(x => (ushort)x.Type == key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (role is ICustomRole { HideSettings: true })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var numChanceOption = Object.Instantiate(__instance.SettingPrefab, __instance.ItemParent);
|
||||
numChanceOption.transform.localPosition = new Vector3(0.044f, 0, 0);
|
||||
numChanceOption.name = role.NiceName;
|
||||
numChanceOption.Role = role;
|
||||
__instance.AllRoleSettings.Add(numChanceOption);
|
||||
|
||||
|
||||
var newTab = Object.Instantiate(tabPrefab, __instance.AdvancedRolesSettings.transform);
|
||||
newTab.name = role.NiceName + " Settings";
|
||||
var toggleSet = Object.Instantiate(newTab.GetComponentInChildren<ToggleOption>(true));
|
||||
var numberSet = Object.Instantiate(newTab.GetComponentInChildren<NumberOption>(true));
|
||||
|
||||
foreach (var option in newTab.GetComponentsInChildren<OptionBehaviour>())
|
||||
{
|
||||
option.gameObject.Destroy();
|
||||
}
|
||||
|
||||
var startOffset = 0.5f;
|
||||
|
||||
foreach (var customOption in CustomOptionsManager.CustomOptions)
|
||||
{
|
||||
if (customOption.AdvancedRole is not null && customOption.AdvancedRole == role.GetType())
|
||||
{
|
||||
startOffset -= 0.5f;
|
||||
|
||||
switch (customOption)
|
||||
{
|
||||
case CustomNumberOption numberOption:
|
||||
var numOpt = numberOption.CreateNumberOption(numberSet, newTab.transform);
|
||||
numOpt.transform.localPosition = new Vector3(-1.25f, startOffset, 0);
|
||||
break;
|
||||
|
||||
case CustomToggleOption toggleOption:
|
||||
var togOpt = toggleOption.CreateToggleOption(toggleSet, newTab.transform);
|
||||
togOpt.transform.localPosition = new Vector3(-1.25f, startOffset, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var tmp = newTab.GetComponentInChildren<TextTranslatorTMP>();
|
||||
tmp.defaultStr = role.NiceName;
|
||||
tmp.TargetText = role.StringName;
|
||||
|
||||
var newAdvSet = new AdvancedRoleSettingsButton
|
||||
{
|
||||
Tab = newTab,
|
||||
Type = (RoleTypes)key
|
||||
};
|
||||
|
||||
__instance.AllAdvancedSettingTabs.Add(newAdvSet);
|
||||
|
||||
toggleSet.gameObject.Destroy();
|
||||
numberSet.gameObject.Destroy();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set custom role options OnValueChanged and re-enable the role settings menu scrolling
|
||||
/// </summary>
|
||||
[HarmonyPostfix, HarmonyPatch("Start")]
|
||||
public static void StartPostfix(RolesSettingsMenu __instance)
|
||||
{
|
||||
foreach (var customOption in CustomOptionsManager.CustomOptions)
|
||||
{
|
||||
if (customOption.AdvancedRole is not null)
|
||||
{
|
||||
customOption.OptionBehaviour.OnValueChanged = (Action<OptionBehaviour>)customOption.ValueChanged;
|
||||
}
|
||||
}
|
||||
|
||||
var scroll = __instance.GetComponentInChildren<Scroller>();
|
||||
scroll.active = true;
|
||||
scroll.ContentYBounds.max = 2 * __instance.AllRoleSettings.Count / 10f;
|
||||
|
||||
scroll.transform.FindChild("UI_Scrollbar").gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update config when value changed
|
||||
/// </summary>
|
||||
[HarmonyPrefix, HarmonyPatch("ValueChanged")]
|
||||
public static bool ValueChangedPrefix(RolesSettingsMenu __instance, [HarmonyArgument(0)] OptionBehaviour obj)
|
||||
{
|
||||
if (obj.GetIl2CppType() != Il2CppType.Of<RoleOptionSetting>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var roleSetting = obj.Cast<RoleOptionSetting>();
|
||||
|
||||
if (roleSetting.Role is not ICustomRole role || role.HideSettings)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
MiraAPIPlugin.Instance.Config.TryGetEntry<int>(role.NumConfigDefinition, out var numEntry);
|
||||
numEntry.Value = roleSetting.RoleMaxCount;
|
||||
|
||||
MiraAPIPlugin.Instance.Config.TryGetEntry<int>(role.ChanceConfigDefinition, out var chanceEntry);
|
||||
chanceEntry.Value = roleSetting.RoleChance;
|
||||
|
||||
roleSetting.UpdateValuesAndText(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions);
|
||||
|
||||
if (AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.Send(new SyncRoleOptionsRpc.Data(role.RoleId, numEntry.Value, chanceEntry.Value));
|
||||
}
|
||||
GameOptionsManager.Instance.GameHostOptions = GameOptionsManager.Instance.CurrentGameOptions;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using Il2CppSystem.Text;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class TaskAdderPatch
|
||||
{
|
||||
public static TaskFolder RolesFolder;
|
||||
|
||||
[HarmonyPostfix, HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.Begin))]
|
||||
public static void AddRolesFolder(TaskAdderGame __instance)
|
||||
{
|
||||
RolesFolder = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||
RolesFolder.gameObject.SetActive(false);
|
||||
RolesFolder.FolderName = "Roles";
|
||||
RolesFolder.name = "RolesFolder";
|
||||
__instance.Root.SubFolders.Add(RolesFolder);
|
||||
__instance.GoToRoot();
|
||||
}
|
||||
|
||||
[HarmonyPostfix, HarmonyPatch(typeof(TaskAddButton), "Role", MethodType.Setter)]
|
||||
public static void RoleGetterPatch(TaskAddButton __instance)
|
||||
{
|
||||
if (__instance.role is ICustomRole lpRole)
|
||||
{
|
||||
if (lpRole.IsOutcast)
|
||||
{
|
||||
__instance.FileImage.color = Color.gray;
|
||||
}
|
||||
}
|
||||
|
||||
__instance.RolloverHandler.OutColor = __instance.FileImage.color;
|
||||
}
|
||||
|
||||
// yes it might be crazy patching the entire method, but i tried so many other methods and only this works :cry:
|
||||
[HarmonyPrefix, HarmonyPatch(typeof(TaskAdderGame), nameof(TaskAdderGame.ShowFolder))]
|
||||
public static bool ShowPatch(TaskAdderGame __instance, [HarmonyArgument(0)] TaskFolder taskFolder)
|
||||
{
|
||||
var stringBuilder = new StringBuilder(64);
|
||||
__instance.Hierarchy.Add(taskFolder);
|
||||
for (var i = 0; i < __instance.Hierarchy.Count; i++)
|
||||
{
|
||||
stringBuilder.Append(__instance.Hierarchy.ToArray()[i].FolderName);
|
||||
stringBuilder.Append("\\");
|
||||
}
|
||||
__instance.PathText.text = stringBuilder.ToString();
|
||||
for (var j = 0; j < __instance.ActiveItems.Count; j++)
|
||||
{
|
||||
Object.Destroy(__instance.ActiveItems.ToArray()[j].gameObject);
|
||||
}
|
||||
__instance.ActiveItems.Clear();
|
||||
var num = 0f;
|
||||
var num2 = 0f;
|
||||
var num3 = 0f;
|
||||
for (var k = 0; k < taskFolder.SubFolders.Count; k++)
|
||||
{
|
||||
var taskFolder2 = Object.Instantiate(taskFolder.SubFolders.ToArray()[k], __instance.TaskParent);
|
||||
var folderTransform = taskFolder2.transform;
|
||||
|
||||
taskFolder2.gameObject.SetActive(true);
|
||||
taskFolder2.Parent = __instance;
|
||||
|
||||
folderTransform.localPosition = new Vector3(num, num2, 0f);
|
||||
folderTransform.localScale = Vector3.one;
|
||||
num3 = Mathf.Max(num3, taskFolder2.Text.bounds.size.y + 1.1f);
|
||||
num += __instance.folderWidth;
|
||||
if (num > __instance.lineWidth)
|
||||
{
|
||||
num = 0f;
|
||||
num2 -= num3;
|
||||
num3 = 0f;
|
||||
}
|
||||
__instance.ActiveItems.Add(folderTransform);
|
||||
if (!taskFolder2 || !taskFolder2.Button)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskFolder2.Button);
|
||||
if (!string.IsNullOrEmpty(__instance.restorePreviousSelectionByFolderName) && taskFolder2.FolderName.Equals(__instance.restorePreviousSelectionByFolderName))
|
||||
{
|
||||
__instance.restorePreviousSelectionFound = taskFolder2.Button;
|
||||
}
|
||||
}
|
||||
|
||||
var flag = false;
|
||||
var list = new List<PlayerTask>();
|
||||
|
||||
foreach (var item in taskFolder.Children)
|
||||
{
|
||||
list.Add(item);
|
||||
}
|
||||
|
||||
for (var l = 0; l < list.Count; l++)
|
||||
{
|
||||
var taskAddButton = Object.Instantiate(__instance.TaskPrefab);
|
||||
taskAddButton.MyTask = list.ToArray()[l];
|
||||
if (taskAddButton.MyTask.TaskType == TaskTypes.DivertPower)
|
||||
{
|
||||
var targetSystem = taskAddButton.MyTask.Cast<DivertPowerTask>().TargetSystem;
|
||||
taskAddButton.Text.text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.DivertPowerTo, DestroyableSingleton<TranslationController>.Instance.GetString(targetSystem));
|
||||
}
|
||||
else if (taskAddButton.MyTask.TaskType == TaskTypes.FixWeatherNode)
|
||||
{
|
||||
var nodeId = ((WeatherNodeTask)taskAddButton.MyTask).NodeId;
|
||||
taskAddButton.Text.text = DestroyableSingleton<TranslationController>.Instance.GetString(StringNames.FixWeatherNode, Array.Empty<Il2CppSystem.Object>()) + " " + DestroyableSingleton<TranslationController>.Instance.GetString(WeatherSwitchGame.ControlNames[nodeId],
|
||||
Array.Empty<Il2CppSystem.Object>());
|
||||
}
|
||||
else
|
||||
{
|
||||
taskAddButton.Text.text = DestroyableSingleton<TranslationController>.Instance.GetString(taskAddButton.MyTask.TaskType);
|
||||
}
|
||||
__instance.AddFileAsChild(taskFolder, taskAddButton, ref num, ref num2, ref num3);
|
||||
if (taskAddButton != null && taskAddButton.Button != null)
|
||||
{
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
|
||||
if (__instance.Hierarchy.Count != 1 && !flag)
|
||||
{
|
||||
var component = ControllerManager.Instance.CurrentUiState.CurrentSelection.GetComponent<TaskFolder>();
|
||||
if (component != null)
|
||||
{
|
||||
__instance.restorePreviousSelectionByFolderName = component.FolderName;
|
||||
}
|
||||
ControllerManager.Instance.SetDefaultSelection(taskAddButton.Button);
|
||||
flag = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (taskFolder.FolderName == "Roles") // idk why only this works???
|
||||
{
|
||||
for (var m = 0; m < DestroyableSingleton<RoleManager>.Instance.AllRoles.Length; m++)
|
||||
{
|
||||
var roleBehaviour = DestroyableSingleton<RoleManager>.Instance.AllRoles[m];
|
||||
if (roleBehaviour.Role != RoleTypes.ImpostorGhost && roleBehaviour.Role != RoleTypes.CrewmateGhost && !roleBehaviour.IsDead)
|
||||
{
|
||||
var taskAddButton2 = Object.Instantiate(__instance.RoleButton);
|
||||
taskAddButton2.SafePositionWorld = __instance.SafePositionWorld;
|
||||
taskAddButton2.Text.text = "Be_" + roleBehaviour.NiceName + ".exe";
|
||||
__instance.AddFileAsChild(RolesFolder, taskAddButton2, ref num, ref num2, ref num3);
|
||||
taskAddButton2.Role = roleBehaviour;
|
||||
if (taskAddButton2 != null && taskAddButton2.Button != null)
|
||||
{
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton2.Button);
|
||||
if (m == 0 && __instance.restorePreviousSelectionFound != null)
|
||||
{
|
||||
ControllerManager.Instance.SetDefaultSelection(__instance.restorePreviousSelectionFound);
|
||||
__instance.restorePreviousSelectionByFolderName = string.Empty;
|
||||
__instance.restorePreviousSelectionFound = null;
|
||||
}
|
||||
else if (m == 0)
|
||||
{
|
||||
ControllerManager.Instance.SetDefaultSelection(taskAddButton2.Button);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (__instance.Hierarchy.Count == 1)
|
||||
{
|
||||
ControllerManager.Instance.SetBackButton(__instance.BackButton);
|
||||
return false;
|
||||
}
|
||||
ControllerManager.Instance.SetBackButton(__instance.FolderBackButton);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(TaskPanelBehaviour))]
|
||||
public static class TaskPanelPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// This patch is to override the automatic updating of the y position on the tab (which is in base game),
|
||||
/// because I can't change the custom tab y pos if it's being overriden every frame.
|
||||
/// Im sure there is an easier/better way, but this is the fix that worked for me
|
||||
/// </summary>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(TaskPanelBehaviour.Update))]
|
||||
public static bool UpdatePrefix(TaskPanelBehaviour __instance)
|
||||
{
|
||||
if (__instance.gameObject.name != "RolePanel")
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var transform = __instance.background.transform;
|
||||
var vector = __instance.background.sprite.bounds.extents;
|
||||
var vector2 = __instance.tab.sprite.bounds.extents;
|
||||
|
||||
transform.localScale = __instance.taskText.textBounds.size.x > 0f ? new Vector3(__instance.taskText.textBounds.size.x + 0.4f, __instance.taskText.textBounds.size.y + 0.3f, 1f) : Vector3.zero;
|
||||
|
||||
vector.y = -vector.y;
|
||||
vector = vector.Mul(transform.localScale);
|
||||
__instance.background.transform.localPosition = vector;
|
||||
|
||||
vector2 = vector2.Mul(__instance.tab.transform.localScale);
|
||||
vector2.y = -vector2.y;
|
||||
vector2.x += vector.x * 2f;
|
||||
__instance.tab.transform.localPosition = vector2;
|
||||
|
||||
if (GameManager.Instance == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
var closePosition = new Vector3(-__instance.background.sprite.bounds.size.x * __instance.background.transform.localScale.x, __instance.closedPosition.y, __instance.closedPosition.z);
|
||||
__instance.closedPosition = closePosition;
|
||||
if (__instance.open)
|
||||
{
|
||||
__instance.timer = Mathf.Min(1f, __instance.timer + Time.deltaTime / __instance.animationTimeSeconds);
|
||||
}
|
||||
else
|
||||
{
|
||||
__instance.timer = Mathf.Max(0f, __instance.timer - Time.deltaTime / __instance.animationTimeSeconds);
|
||||
}
|
||||
Vector3 relativePos;
|
||||
relativePos = new Vector3(Mathf.SmoothStep(__instance.closedPosition.x, __instance.openPosition.x, __instance.timer), Mathf.SmoothStep(__instance.closedPosition.y, __instance.openPosition.y, __instance.timer), __instance.openPosition.z);
|
||||
__instance.transform.localPosition = AspectPosition.ComputePosition(AspectPosition.EdgeAlignments.LeftTop, relativePos);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime;
|
||||
using MiraAPI.Networking.Options;
|
||||
using Reactor.Localization.Utilities;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
public static class CustomRoleManager
|
||||
{
|
||||
public static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
|
||||
|
||||
public static void RegisterInRoleManager()
|
||||
{
|
||||
RoleManager.Instance.AllRoles = RoleManager.Instance.AllRoles.Concat(CustomRoles.Values).ToArray();
|
||||
}
|
||||
|
||||
internal static void RegisterRole(Type roleType)
|
||||
{
|
||||
if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (CustomRoles.Any(behaviour => behaviour.GetType() == roleType))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var roleBehaviour = (RoleBehaviour)new GameObject(roleType.Name).DontDestroy().AddComponent(Il2CppType.From(roleType));
|
||||
|
||||
if (roleBehaviour is not ICustomRole customRole)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
roleBehaviour.Role = (RoleTypes)customRole.RoleId;
|
||||
roleBehaviour.TeamType = customRole.Team;
|
||||
roleBehaviour.NameColor = customRole.RoleColor;
|
||||
roleBehaviour.StringName = CustomStringName.CreateAndRegister(customRole.RoleName);
|
||||
roleBehaviour.BlurbName = CustomStringName.CreateAndRegister(customRole.RoleDescription);
|
||||
roleBehaviour.BlurbNameLong = CustomStringName.CreateAndRegister(customRole.RoleLongDescription);
|
||||
roleBehaviour.AffectedByLightAffectors = customRole.AffectedByLight;
|
||||
roleBehaviour.CanBeKilled = customRole.CanGetKilled;
|
||||
roleBehaviour.CanUseKillButton = customRole.CanKill;
|
||||
roleBehaviour.TasksCountTowardProgress = customRole.TasksCount;
|
||||
roleBehaviour.CanVent = customRole.CanUseVent;
|
||||
roleBehaviour.DefaultGhostRole = customRole.GhostRole;
|
||||
roleBehaviour.MaxCount = 15;
|
||||
|
||||
if (customRole.IsGhostRole)
|
||||
{
|
||||
RoleManager.GhostRoles.Add(roleBehaviour.Role);
|
||||
}
|
||||
|
||||
CustomRoles.Add(customRole.RoleId, roleBehaviour);
|
||||
|
||||
if (customRole.HideSettings)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var config = PluginSingleton<MiraAPIPlugin>.Instance.Config;
|
||||
config.Bind(customRole.NumConfigDefinition, 1);
|
||||
config.Bind(customRole.ChanceConfigDefinition, 100);
|
||||
|
||||
}
|
||||
|
||||
public static bool GetCustomRoleBehaviour(RoleTypes roleType, out ICustomRole result)
|
||||
{
|
||||
CustomRoles.TryGetValue((ushort)roleType, out var temp);
|
||||
if (temp is ICustomRole role)
|
||||
{
|
||||
result = role;
|
||||
return true;
|
||||
}
|
||||
|
||||
result = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
public static TaskPanelBehaviour CreateRoleTab(ICustomRole role)
|
||||
{
|
||||
var ogPanel = HudManager.Instance.TaskStuff.transform.FindChild("TaskPanel").gameObject.GetComponent<TaskPanelBehaviour>();
|
||||
var clonePanel = Object.Instantiate(ogPanel.gameObject, ogPanel.transform.parent);
|
||||
clonePanel.name = "RolePanel";
|
||||
|
||||
var newPanel = clonePanel.GetComponent<TaskPanelBehaviour>();
|
||||
newPanel.open = false;
|
||||
|
||||
var tab = newPanel.tab.gameObject;
|
||||
tab.GetComponentInChildren<TextTranslatorTMP>().Destroy();
|
||||
|
||||
newPanel.transform.localPosition = ogPanel.transform.localPosition - new Vector3(0, 1, 0);
|
||||
|
||||
UpdateRoleTab(newPanel, role);
|
||||
return newPanel;
|
||||
}
|
||||
|
||||
public static void UpdateRoleTab(TaskPanelBehaviour panel, ICustomRole role)
|
||||
{
|
||||
var tabText = panel.tab.gameObject.GetComponentInChildren<TextMeshPro>();
|
||||
var ogPanel = HudManager.Instance.TaskStuff.transform.FindChild("TaskPanel").gameObject.GetComponent<TaskPanelBehaviour>();
|
||||
if (tabText.text != role.RoleName)
|
||||
{
|
||||
tabText.text = role.RoleName;
|
||||
}
|
||||
|
||||
var y = ogPanel.taskText.textBounds.size.y + 1;
|
||||
panel.closedPosition = new Vector3(ogPanel.closedPosition.x, ogPanel.open ? y + 0.2f : 2f, ogPanel.closedPosition.z);
|
||||
panel.openPosition = new Vector3(ogPanel.openPosition.x, ogPanel.open ? y : 2f, ogPanel.openPosition.z);
|
||||
|
||||
panel.SetTaskText(role.SetTabText().ToString());
|
||||
}
|
||||
|
||||
public static void SyncRoleSettings()
|
||||
{
|
||||
foreach (var role in CustomRoles.Values.Select(x => (ICustomRole)x))
|
||||
{
|
||||
if (role.HideSettings)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(role.NumConfigDefinition, out var numEntry);
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(role.ChanceConfigDefinition, out var chanceEntry);
|
||||
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.Send(new SyncRoleOptionsRpc.Data(role.RoleId, numEntry.Value, chanceEntry.Value));
|
||||
}
|
||||
}
|
||||
|
||||
public static void SyncRoleSettings(int targetId)
|
||||
{
|
||||
foreach (var role in CustomRoles.Values.Select(x => (ICustomRole)x))
|
||||
{
|
||||
if (role.HideSettings)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(role.NumConfigDefinition, out var numEntry);
|
||||
PluginSingleton<MiraAPIPlugin>.Instance.Config.TryGetEntry<int>(role.ChanceConfigDefinition, out var chanceEntry);
|
||||
|
||||
Rpc<SyncRoleOptionsRpc>.Instance.SendTo(targetId, new SyncRoleOptionsRpc.Data(role.RoleId, numEntry.Value, chanceEntry.Value));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
using AmongUs.GameOptions;
|
||||
using BepInEx.Configuration;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
public interface ICustomRole
|
||||
{
|
||||
string RoleName { get; }
|
||||
|
||||
ushort RoleId { get; }
|
||||
|
||||
string RoleDescription { get; }
|
||||
|
||||
string RoleLongDescription { get; }
|
||||
|
||||
Color RoleColor { get; }
|
||||
|
||||
RoleTeamTypes Team { get; }
|
||||
|
||||
LoadableAsset<Sprite> Icon => MiraAssets.Empty;
|
||||
|
||||
ConfigDefinition NumConfigDefinition => new("Roles", $"Num{RoleName}");
|
||||
|
||||
ConfigDefinition ChanceConfigDefinition => new("Roles", $"Chance{RoleName}");
|
||||
|
||||
bool AffectedByLight => Team == RoleTeamTypes.Crewmate;
|
||||
|
||||
bool CanGetKilled => Team == RoleTeamTypes.Crewmate;
|
||||
|
||||
bool IsOutcast => false;
|
||||
|
||||
bool CanKill => Team == RoleTeamTypes.Impostor;
|
||||
|
||||
bool CanUseVent => Team == RoleTeamTypes.Impostor;
|
||||
|
||||
bool TasksCount => Team == RoleTeamTypes.Crewmate;
|
||||
|
||||
bool IsGhostRole => false;
|
||||
|
||||
bool TargetsBodies => false;
|
||||
|
||||
bool CreateCustomTab => true;
|
||||
|
||||
bool HideSettings => IsGhostRole;
|
||||
|
||||
RoleTypes GhostRole => Team == RoleTeamTypes.Crewmate ? RoleTypes.CrewmateGhost : RoleTypes.ImpostorGhost;
|
||||
|
||||
void CreateOptions() { }
|
||||
|
||||
void PlayerControlFixedUpdate(PlayerControl playerControl) { }
|
||||
|
||||
void HudUpdate(HudManager hudManager) { }
|
||||
|
||||
string GetCustomEjectionMessage(GameData.PlayerInfo player)
|
||||
{
|
||||
return Team == RoleTeamTypes.Impostor ? $"{player.PlayerName} was The {RoleName}" : null;
|
||||
}
|
||||
|
||||
StringBuilder SetTabText()
|
||||
{
|
||||
var taskStringBuilder = Helpers.CreateForRole(this);
|
||||
return taskStringBuilder;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
|
||||
namespace MiraAPI.Roles;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class)]
|
||||
public class RegisterCustomRoleAttribute : Attribute
|
||||
{
|
||||
private static readonly HashSet<Assembly> RegisteredAssemblies = [];
|
||||
|
||||
public static void Register(Assembly assembly)
|
||||
{
|
||||
if (!RegisteredAssemblies.Add(assembly)) return;
|
||||
|
||||
foreach (var type in assembly.GetTypes())
|
||||
{
|
||||
var attribute = type.GetCustomAttribute<RegisterCustomRoleAttribute>();
|
||||
if (attribute != null)
|
||||
{
|
||||
CustomRoleManager.RegisterRole(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
namespace MiraAPI.Utilities
|
||||
{
|
||||
public abstract class LoadableAsset<T> where T : UnityEngine.Object
|
||||
{
|
||||
protected T _loadedAsset;
|
||||
public abstract T LoadAsset();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities
|
||||
{
|
||||
public class LoadableBundleAsset<T>(string name, AssetBundle bundle) : LoadableAsset<T> where T : UnityEngine.Object
|
||||
{
|
||||
public override T LoadAsset()
|
||||
{
|
||||
if (_loadedAsset != null)
|
||||
{
|
||||
return _loadedAsset;
|
||||
}
|
||||
|
||||
return _loadedAsset = bundle.LoadAsset<T>(name);
|
||||
throw new Exception($"INVALID ASSET: {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets
|
||||
{
|
||||
public class LoadableResourceAsset(string name, string resourcesFolder) : LoadableAsset<Sprite>
|
||||
{
|
||||
public override Sprite LoadAsset()
|
||||
{
|
||||
if (_loadedAsset != null)
|
||||
{
|
||||
return _loadedAsset;
|
||||
}
|
||||
|
||||
return _loadedAsset = SpriteTools.LoadSpriteFromPath(resourcesFolder + name);
|
||||
throw new Exception($"INVALID ASSET: {name}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
namespace MiraAPI.Utilities.Assets
|
||||
{
|
||||
public static class MiraAssets
|
||||
{
|
||||
public static readonly LoadableResourceAsset Empty = new("", "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities.Assets
|
||||
{
|
||||
public static class SpriteTools
|
||||
{
|
||||
private delegate bool DLoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
|
||||
|
||||
private static DLoadImage _iCallLoadImage;
|
||||
|
||||
private static bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
_iCallLoadImage ??= IL2CPP.ResolveICall<DLoadImage>("UnityEngine.ImageConversion::LoadImage");
|
||||
|
||||
var il2CPPArray = (Il2CppStructArray<byte>)data;
|
||||
|
||||
return _iCallLoadImage.Invoke(tex.Pointer, il2CPPArray.Pointer, markNonReadable);
|
||||
}
|
||||
|
||||
public static Sprite LoadSpriteFromPath(string resourcePath)
|
||||
{
|
||||
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false);
|
||||
var assembly = Assembly.GetExecutingAssembly();
|
||||
var myStream = assembly.GetManifestResourceStream(resourcePath);
|
||||
if (myStream != null)
|
||||
{
|
||||
var buttonTexture = myStream.ReadFully();
|
||||
LoadImage(tex, buttonTexture, false);
|
||||
}
|
||||
|
||||
return Sprite.Create(tex, new Rect(0, 0, tex.width, tex.height), new Vector2(0.5f, 0.5f));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
using MiraAPI.Roles;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities
|
||||
{
|
||||
public static class Extensions
|
||||
{
|
||||
public static void UpdateBodies(this PlayerControl playerControl, Color outlineColor, ref DeadBody target)
|
||||
{
|
||||
foreach (var body in Object.FindObjectsOfType<DeadBody>())
|
||||
{
|
||||
foreach (var bodyRenderer in body.bodyRenderers)
|
||||
{
|
||||
bodyRenderer.SetOutline(null);
|
||||
}
|
||||
}
|
||||
|
||||
if (playerControl.Data.Role is not ICustomRole { TargetsBodies: true })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
target = playerControl.NearestDeadBody();
|
||||
if (!target)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var renderer in target.bodyRenderers)
|
||||
{
|
||||
renderer.SetOutline(outlineColor);
|
||||
}
|
||||
}
|
||||
|
||||
public static DeadBody NearestDeadBody(this PlayerControl playerControl)
|
||||
{
|
||||
var results = new Il2CppSystem.Collections.Generic.List<Collider2D>();
|
||||
Physics2D.OverlapCircle(playerControl.GetTruePosition(), playerControl.MaxReportDistance / 4f, Helpers.Filter, results);
|
||||
return results.ToArray()
|
||||
.Where(collider2D => collider2D.CompareTag("DeadBody"))
|
||||
.Select(collider2D => collider2D.GetComponent<DeadBody>())
|
||||
.FirstOrDefault(component => component && !component.Reported);
|
||||
}
|
||||
|
||||
public static PlayerControl GetClosestPlayer(this PlayerControl playerControl, bool includeImpostors, float distance)
|
||||
{
|
||||
PlayerControl result = null;
|
||||
if (!ShipStatus.Instance)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var truePosition = playerControl.GetTruePosition();
|
||||
|
||||
foreach (var playerInfo in GameData.Instance.AllPlayers)
|
||||
{
|
||||
if (playerInfo.Disconnected || playerInfo.PlayerId == playerControl.PlayerId ||
|
||||
playerInfo.IsDead || !includeImpostors && playerInfo.Role.IsImpostor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var @object = playerInfo.Object;
|
||||
if (!@object)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var vector = @object.GetTruePosition() - truePosition;
|
||||
var magnitude = vector.magnitude;
|
||||
if (!(magnitude <= distance) || PhysicsHelpers.AnyNonTriggersBetween(truePosition,
|
||||
vector.normalized,
|
||||
magnitude, LayerMask.GetMask("Ship", "Objects")))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
result = @object;
|
||||
distance = magnitude;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using MiraAPI.Roles;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Utilities
|
||||
{
|
||||
public static class Helpers
|
||||
{
|
||||
public static readonly ContactFilter2D Filter = ContactFilter2D.CreateLegacyFilter(Constants.NotShipMask, float.MinValue, float.MaxValue);
|
||||
|
||||
public static PlainShipRoom GetRoom(Vector3 pos)
|
||||
{
|
||||
return ShipStatus.Instance.AllRooms.ToList().Find(room => room.roomArea.OverlapPoint(pos));
|
||||
}
|
||||
|
||||
public static string GetSuffix(NumberSuffixes suffix)
|
||||
{
|
||||
return suffix switch
|
||||
{
|
||||
NumberSuffixes.None => string.Empty,
|
||||
NumberSuffixes.Multiplier => "x",
|
||||
NumberSuffixes.Seconds => "s",
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
|
||||
public static StringBuilder CreateForRole(ICustomRole role)
|
||||
{
|
||||
var taskStringBuilder = new StringBuilder();
|
||||
taskStringBuilder.AppendLine($"{role.RoleColor.ToTextColor()}You are a <b>{role.RoleName}.</b></color>");
|
||||
taskStringBuilder.Append("<size=70%>");
|
||||
taskStringBuilder.AppendLine($"{role.RoleLongDescription}");
|
||||
return taskStringBuilder;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user