mirror of
https://github.com/All-Of-Us-Mods/MiraAPI.git
synced 2026-10-21 13:37:54 +00:00
Compare commits
93
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5049d16ac9 | ||
|
|
dccd06eac3 | ||
|
|
bf9b34e728 | ||
|
|
baf80618bb | ||
|
|
7d1976f772 | ||
|
|
4ce24c2a00 | ||
|
|
7fa75946fd | ||
|
|
43f92d4dc2 | ||
|
|
ee22b776eb | ||
|
|
f31349f6e8 | ||
|
|
448e2f9152 | ||
|
|
dde7e70406 | ||
|
|
713a7a2bcc | ||
|
|
4b144a782d | ||
|
|
7a8d608b17 | ||
|
|
6503bbecb7 | ||
|
|
cb794c3fc2 | ||
|
|
88329bef79 | ||
|
|
eb7abecd75 | ||
|
|
3c51c88963 | ||
|
|
e42f5e6029 | ||
|
|
61be7dda82 | ||
|
|
3220a4a820 | ||
|
|
8fd142a92e | ||
|
|
fcec6ad6e5 | ||
|
|
10a7ffe886 | ||
|
|
7e541733b7 | ||
|
|
99a275a97c | ||
|
|
67a5f0dd67 | ||
|
|
f13d46ec18 | ||
|
|
8bf101534f | ||
|
|
479323acdd | ||
|
|
50c5899f4d | ||
|
|
fd719ae42f | ||
|
|
e050b043cb | ||
|
|
e78c456636 | ||
|
|
7e8d07672c | ||
|
|
77ae01c66a | ||
|
|
ce824f05fa | ||
|
|
b5fe3c7afd | ||
|
|
1750cd3db9 | ||
|
|
4dd2554b38 | ||
|
|
e5cfd8a2fc | ||
|
|
6d387cae96 | ||
|
|
59f3058685 | ||
|
|
9dfb40d7bf | ||
|
|
ffca13e6e6 | ||
|
|
c1d2868e77 | ||
|
|
18c52e0138 | ||
|
|
b64bf7c9de | ||
|
|
e98c16b9f5 | ||
|
|
e166efa666 | ||
|
|
8da80be027 | ||
|
|
af75cc3606 | ||
|
|
0f1962394a | ||
|
|
36991f34ee | ||
|
|
ae5f3783b4 | ||
|
|
12a1908c3e | ||
|
|
8246e05da6 | ||
|
|
c75e039141 | ||
|
|
ac66e0d62b | ||
|
|
c4cbf0b18d | ||
|
|
f3175990d3 | ||
|
|
790d868759 | ||
|
|
9f11874189 | ||
|
|
1b6a2c4eb3 | ||
|
|
81887c34d9 | ||
|
|
810fd54e10 | ||
|
|
5492c2b3cc | ||
|
|
f7a515882a | ||
|
|
89eb774501 | ||
|
|
5fd57633ff | ||
|
|
da7aab9aa2 | ||
|
|
3d274c2f61 | ||
|
|
b94e20c6f7 | ||
|
|
5a0428c7c0 | ||
|
|
c41dd27f88 | ||
|
|
6872ca6866 | ||
|
|
af3d99eafe | ||
|
|
9d130a53de | ||
|
|
2c2a2207c5 | ||
|
|
960573243c | ||
|
|
94bb5a7a37 | ||
|
|
5eba77dba9 | ||
|
|
ef329bfcbd | ||
|
|
a95cac055f | ||
|
|
7909b8f13d | ||
|
|
8ad2e220c9 | ||
|
|
40d88750da | ||
|
|
28636e961d | ||
|
|
5eba1c9d7f | ||
|
|
7cbf9ffe1b | ||
|
|
38ec50bcc7 |
No files matched your search
+2
-1
@@ -6,4 +6,5 @@ bin/
|
||||
.qodo/
|
||||
|
||||
*.user
|
||||
*.lock.json
|
||||
*.lock.json
|
||||
.qodo
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Reactor" Version="2.3.1" />
|
||||
<PackageReference Include="BepInEx.Unity.IL2CPP" Version="6.0.0-be.735" Private="false" ExcludeAssets="runtime;native" />
|
||||
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.9.9" PrivateAssets="all" />
|
||||
<PackageReference Include="AmongUs.GameLibs.Steam" Version="2025.10.14" PrivateAssets="all" />
|
||||
|
||||
<PackageReference Include="BepInEx.AutoPlugin" Version="1.1.0" PrivateAssets="all" />
|
||||
<PackageReference Include="BepInEx.IL2CPP.MSBuild" Version="2.1.0-rc.1" PrivateAssets="all" ExcludeAssets="runtime" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
|
||||
<VersionPrefix>0.2.1</VersionPrefix>
|
||||
<VersionPrefix>0.3.3</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
|
||||
<Authors>All Of Us, XtraCube, Angxl</Authors>
|
||||
|
||||
@@ -19,11 +19,14 @@ public class ExampleOptions : AbstractOptionGroup
|
||||
[ModdedNumberOption("Number Opt", min: 0, max: 10, increment: .25f, formatString: "0.00", suffixType: MiraNumberSuffixes.Percent)]
|
||||
public float NumberOpt { get; set; } = 4f;
|
||||
|
||||
[ModdedEnumOption("Best API", typeof(BestApi), ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public BestApi Opt { get; set; } = BestApi.MiraAPI;
|
||||
[ModdedEnumOption("Best API", typeof(AuApis), ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public AuApis BestApiOpt { get; set; } = AuApis.MiraAPI;
|
||||
|
||||
[ModdedEnumOption<AuApis>("Worst API", ["Mira API", "Mitochondria", "Reactor"])]
|
||||
public AuApis WorstApiOpt { get; set; } = AuApis.Mitochondria;
|
||||
}
|
||||
|
||||
public enum BestApi
|
||||
public enum AuApis
|
||||
{
|
||||
MiraAPI,
|
||||
Mitochondria,
|
||||
|
||||
@@ -23,6 +23,12 @@ public class ExampleOptions2 : AbstractOptionGroup
|
||||
{
|
||||
ChangedEvent = x => Logger<ExamplePlugin>.Info($"changed Enum Opt to {x}"),
|
||||
};
|
||||
|
||||
public ModdedMultiSelectOption<TestingData> MultiSelectOption { get; } = new(
|
||||
"Emotions",
|
||||
TestingData.Happy,
|
||||
TestingData.All,
|
||||
TestingData.None);
|
||||
}
|
||||
|
||||
public enum TestingData : ulong
|
||||
@@ -30,4 +36,6 @@ public enum TestingData : ulong
|
||||
Happy,
|
||||
Sad,
|
||||
Neutral,
|
||||
All,
|
||||
None,
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using MiraAPI.Example.GameOver;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Example.GameOver;
|
||||
using MiraAPI.GameEnd;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
@@ -22,6 +23,7 @@ public class NeutralKillerRole : ImpostorRole, ICustomRole
|
||||
|
||||
public RoleOptionsGroup RoleOptionsGroup { get; } = new("Outcast", Color.gray);
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
public TeamIntroConfiguration? IntroConfiguration { get; } = new(
|
||||
Color.gray,
|
||||
"OUTCAST",
|
||||
|
||||
@@ -34,7 +34,7 @@ public static class MiraEventManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
|
||||
Error($"Error invoking event handler for {typeof(T).Name}: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public static class MiraEventManager
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
|
||||
Error($"Error invoking event handler for {type.Name}: {ex.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -30,13 +30,13 @@ public static class GameOverManager
|
||||
|
||||
if (gameOverType.IsAbstract)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("The type must not be abstract.");
|
||||
Error("The type must not be abstract.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (gameOverType.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("The type must have a parameterless constructor.");
|
||||
Error("The type must have a parameterless constructor.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ public static class CustomGameModeManager
|
||||
return;
|
||||
}
|
||||
|
||||
Logger<MiraApiPlugin>.Error($"No gamemode with id {id} found!");
|
||||
Error($"No gamemode with id {id} found!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,7 +48,7 @@ public static class CustomGameModeManager
|
||||
{
|
||||
if (!typeof(CustomGameMode).IsAssignableFrom(gameModeType))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
|
||||
Warning($"{gameModeType.Name} does not inherit CustomGameMode!");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -56,13 +56,13 @@ public static class CustomGameModeManager
|
||||
|
||||
if (modeObj is not CustomGameMode gameMode)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create instance of {gameModeType.Name}");
|
||||
Error($"Failed to create instance of {gameModeType.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (GameModes.Any(x => x.Key == gameMode.Id))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"ID for gamemode {gameMode.Name} already exists!");
|
||||
Error($"ID for gamemode {gameMode.Name} already exists!");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using MiraAPI.Modifiers;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
@@ -28,11 +29,16 @@ public abstract class AbstractOptionGroup
|
||||
public virtual Type? OptionableType => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the group should be shown in the modifiers menu.
|
||||
/// Gets a value indicating whether the group should be shown in the modifiers menu. This is deprecated, please use ParentMenu!
|
||||
/// </summary>
|
||||
// TODO: make this not a boolean
|
||||
public virtual bool ShowInModifiersMenu => false;
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating which menu the group is in.
|
||||
/// </summary>
|
||||
public virtual MenuCategory ParentMenu => MenuCategory.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the function that determines whether the group should be visible or not.
|
||||
/// </summary>
|
||||
@@ -62,4 +68,33 @@ public abstract class AbstractOptionGroup<T> : AbstractOptionGroup where T : IOp
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override Type OptionableType => typeof(T);
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating which menu the group is in.
|
||||
/// </summary>
|
||||
public override MenuCategory ParentMenu
|
||||
{
|
||||
get
|
||||
{
|
||||
var type = typeof(T);
|
||||
if (type == typeof(BaseModifier))
|
||||
{
|
||||
return MenuCategory.Modifiers;
|
||||
}
|
||||
else if (type == typeof(RoleBehaviour))
|
||||
{
|
||||
return MenuCategory.Roles;
|
||||
}
|
||||
return MenuCategory.Game;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum MenuCategory
|
||||
{
|
||||
Game,
|
||||
Roles,
|
||||
Modifiers,
|
||||
CustomOne,
|
||||
CustomTwo
|
||||
}
|
||||
@@ -32,3 +32,33 @@ public class ModdedEnumOptionAttribute(string title, Type enumType, string[]? va
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" with EnumType ${enumType.FullName} is not a ModdedEnumOption");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generic attribute for creating an enum option.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">An enum type.</typeparam>
|
||||
[AttributeUsage(AttributeTargets.Property)]
|
||||
public class ModdedEnumOptionAttribute<T>(string title, string[]? values = null)
|
||||
: ModdedOptionAttribute(title) where T : struct, Enum
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
var opt = new ModdedEnumOption<T>(Title, (T)(value ?? 0), values);
|
||||
return opt;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
var opt = HolderOption as ModdedEnumOption;
|
||||
opt?.SetValue((int)value);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override object GetValue()
|
||||
{
|
||||
return HolderOption is ModdedEnumOption<T> opt
|
||||
? Enum.ToObject(typeof(T), opt.Value)
|
||||
: throw new InvalidOperationException($"HolderOption for option \"{Title}\" is not a ModdedEnumOption<{typeof(T).Name}>");
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,15 @@ public class ModdedNumberOptionAttribute(
|
||||
{
|
||||
internal override IModdedOption CreateOption(object? value, PropertyInfo property)
|
||||
{
|
||||
return new ModdedNumberOption(Title, (float)(value ?? min+increment), min, max, increment, suffixType, formatString, zeroInfinity);
|
||||
return new ModdedNumberOption(
|
||||
Title,
|
||||
(float)(value ?? min + increment),
|
||||
min,
|
||||
max,
|
||||
increment,
|
||||
suffixType,
|
||||
formatString,
|
||||
zeroInfinity);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -92,6 +92,11 @@ public interface IModdedOption
|
||||
/// <param name="saveDefault">Indicates whether to save the default value instead of the current value.</param>
|
||||
void SaveToPreset(ConfigFile presetConfig, bool saveDefault = false);
|
||||
|
||||
/// <summary>
|
||||
/// Changes the scales and positions of the option if it's attached to the game options menu.
|
||||
/// </summary>
|
||||
void ChangeGameSetting();
|
||||
|
||||
/// <summary>
|
||||
/// Binds the option to a configuration file.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using MiraAPI.GameOptions.OptionTypes;
|
||||
using Reactor.Utilities.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.GameOptions;
|
||||
|
||||
/// <summary>
|
||||
/// Component added onto options generated via MiraAPI. This allows developers to hook patches onto options in general, without using a list.
|
||||
/// </summary>
|
||||
[RegisterInIl2Cpp]
|
||||
public class MiraNumberOptionComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the modded option associated with the object.
|
||||
/// </summary>
|
||||
public ModdedNumberOption NumberOption { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the default increment.
|
||||
/// </summary>
|
||||
public float DefaultIncrement { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the value of whether or not holding shift will split the increment in half.
|
||||
/// </summary>
|
||||
public bool ShiftIncrementToggle { get; set; }
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public static class ModdedOptionsManager
|
||||
|
||||
if (TypeToGroup.ContainsKey(type))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Group {type.Name} already exists.");
|
||||
Error($"Group {type.Name} already exists.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -47,11 +47,13 @@ public static class ModdedOptionsManager
|
||||
TypeToGroup.Add(type, group);
|
||||
pluginInfo.InternalOptionGroups.Add(group);
|
||||
|
||||
#pragma warning disable S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||
typeof(OptionGroupSingleton<>).MakeGenericType(type)
|
||||
#pragma warning disable S3011
|
||||
.GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic)!
|
||||
#pragma warning restore S3011
|
||||
.SetValue(null, group);
|
||||
#pragma warning restore S3011 // Reflection should not be used to increase accessibility of classes, methods, or fields
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -60,13 +62,13 @@ public static class ModdedOptionsManager
|
||||
{
|
||||
if (!TypeToGroup.TryGetValue(type, out var group))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
|
||||
Error($"Failed to get group for {type.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (property.GetValue(group) is not IModdedOption option)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
|
||||
Error($"Failed to get option for {property.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -81,13 +83,13 @@ public static class ModdedOptionsManager
|
||||
{
|
||||
if (OptionAttributes.ContainsKey(property))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Property {property.Name} already has an attribute registered.");
|
||||
Error($"Property {property.Name} already has an attribute registered.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!TypeToGroup.TryGetValue(type, out var group))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to get group for {type.Name}");
|
||||
Error($"Failed to get group for {type.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,7 +97,7 @@ public static class ModdedOptionsManager
|
||||
|
||||
if (option == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to get option for {property.Name}");
|
||||
Error($"Failed to get option for {property.Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,17 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
stringOption.name =
|
||||
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
|
||||
|
||||
stringOption.SetUpFromData(Data, 20);
|
||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
||||
// Changing the button text to make them more distinct from number options
|
||||
stringOption.MinusBtn.ChangeButtonText("<");
|
||||
stringOption.PlusBtn.ChangeButtonText(">");
|
||||
|
||||
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||
stringOption.Title = StringName;
|
||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||
stringOption.Value = Value;
|
||||
@@ -61,6 +67,32 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
return stringOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeGameSetting()
|
||||
{
|
||||
var stringOption = OptionBehaviour as StringOption;
|
||||
|
||||
stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||
stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||
|
||||
// The value box itself becomes the button
|
||||
stringOption.PlusBtn.gameObject.SetActive(false);
|
||||
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||
|
||||
var background = stringOption.transform.GetChild(0);
|
||||
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||
background.localScale += new Vector3(1f, 0f, 0f);
|
||||
|
||||
var title = stringOption.TitleText;
|
||||
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||
title.fontSize = 2.9f; // Why is it different for string options??
|
||||
|
||||
var valueBox = stringOption.transform.GetChild(5);
|
||||
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
@@ -105,7 +137,7 @@ public class ModdedEnumOption : ModdedOption<int>
|
||||
/// An option for selecting an enum value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
public class ModdedEnumOption<T> : ModdedOption<T> where T : struct, Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
@@ -116,12 +148,12 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// Initializes a new instance of the <see cref="ModdedEnumOption{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="defaultValue">The default value.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
public ModdedEnumOption(string title, T defaultValue, string[]? values = null, bool includeInPreset=true) : base(title, defaultValue, includeInPreset)
|
||||
{
|
||||
Values = values ?? Enum.GetNames(typeof(T));
|
||||
Values = values ?? Enum.GetNames<T>();
|
||||
Data = ScriptableObject.CreateInstance<StringGameSetting>();
|
||||
var data = (StringGameSetting)Data;
|
||||
|
||||
@@ -138,11 +170,17 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
stringOption.name =
|
||||
$"{ParentMod!.OptionsTitleText}.EnumOption.{TranslationController.Instance.GetString(StringName)}";
|
||||
|
||||
stringOption.SetUpFromData(Data, 20);
|
||||
stringOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
// SetUpFromData method doesnt work correctly so we must set the values manually
|
||||
// Changing the button text to make them more distinct from number options
|
||||
stringOption.MinusBtn.ChangeButtonText("<");
|
||||
stringOption.PlusBtn.ChangeButtonText(">");
|
||||
|
||||
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||
stringOption.Title = StringName;
|
||||
stringOption.Values = (Data as StringGameSetting)?.Values ?? new Il2CppStructArray<StringNames>(0);
|
||||
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
@@ -152,6 +190,32 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
return stringOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeGameSetting()
|
||||
{
|
||||
var stringOption = OptionBehaviour as StringOption;
|
||||
|
||||
stringOption!.MinusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||
stringOption.PlusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||
|
||||
// The value box itself becomes the button
|
||||
stringOption.PlusBtn.gameObject.SetActive(false);
|
||||
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||
|
||||
var background = stringOption.transform.GetChild(0);
|
||||
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||
background.localScale += new Vector3(1f, 0f, 0f);
|
||||
|
||||
var title = stringOption.TitleText;
|
||||
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||
title.fontSize = 2.9f; // Why is it different for string options??
|
||||
|
||||
var valueBox = stringOption.transform.GetChild(5);
|
||||
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
@@ -167,19 +231,19 @@ public class ModdedEnumOption<T> : ModdedOption<T> where T : Enum
|
||||
/// <inheritdoc />
|
||||
public override void HandleNetData(byte[] data)
|
||||
{
|
||||
SetValue((T)Enum.Parse(typeof(T), Encoding.Unicode.GetString(data)));
|
||||
SetValue(Enum.Parse<T>(Encoding.Unicode.GetString(data)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return (T)Enum.Parse(typeof(T), optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||
return Enum.Parse<T>(optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(T newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,182 @@
|
||||
using AmongUs.GameOptions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.Modules;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.GameOptions.OptionTypes;
|
||||
|
||||
/// <summary>
|
||||
/// An option for selecting an enum value.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
public class ModdedMultiSelectOption<T> : ModdedOption<T> where T : struct, Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the string values of the enum.
|
||||
/// </summary>
|
||||
public string[]? Values { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating "all of the above".
|
||||
/// </summary>
|
||||
public T All { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating "none of the above".
|
||||
/// </summary>
|
||||
public T None { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the list of buttons that relate to the current instance.
|
||||
/// </summary>
|
||||
public List<MonoBehaviour> Buttons { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedMultiSelectOption{T}"/> class.
|
||||
/// </summary>
|
||||
/// <param name="title">The title of the option.</param>
|
||||
/// <param name="defaultValue">The default value as an int.</param>
|
||||
/// <param name="all">The all value.</param>
|
||||
/// <param name="none">The none value.</param>
|
||||
/// <param name="values">An option list of string values to use in place of the enum name.</param>
|
||||
/// <exception cref="ArgumentException">The enum type doesn't have the flags attribute.</exception>
|
||||
public ModdedMultiSelectOption(string title, T defaultValue, T all, T none, string[]? values = null) : base(title, defaultValue)
|
||||
{
|
||||
var tType = typeof(T);
|
||||
|
||||
if (tType.GetCustomAttribute<FlagsAttribute>() == null)
|
||||
throw new ArgumentException($"{tType.Name} is not a flag enum type");
|
||||
|
||||
Values = values ?? Enum.GetNames<T>();
|
||||
All = all;
|
||||
None = none;
|
||||
Data = ScriptableObject.CreateInstance<MultiSelectGameSetting>();
|
||||
Buttons = [];
|
||||
|
||||
var data = (MultiSelectGameSetting)Data;
|
||||
data.Title = StringName;
|
||||
data.OptionName = Int32OptionNames.Invalid;
|
||||
data.Type = global::OptionTypes.MultipleChoice; // How nice of the game to provide an enum value themselves
|
||||
data.EnumNames = []; // We don't initialise the translation here, we just add one as and when needed
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override OptionBehaviour CreateOption(
|
||||
ToggleOption toggleOpt,
|
||||
NumberOption numberOpt,
|
||||
StringOption stringOpt,
|
||||
PlayerOption playerOpt,
|
||||
Transform container)
|
||||
{
|
||||
var stringOption = Object.Instantiate(stringOpt, container);
|
||||
|
||||
stringOption.SetUpFromData(Data, 20);
|
||||
stringOption.OnValueChanged = (Action<OptionBehaviour>)ValueChanged;
|
||||
|
||||
var toggle = Object.Instantiate(toggleOpt.GetComponentInChildren<PassiveButton>(), stringOption.transform);
|
||||
toggle.name = "Button";
|
||||
toggle.transform.DestroyChildren();
|
||||
toggle.OverrideOnClickListeners(ToggleButtons);
|
||||
|
||||
var box = toggle.GetComponent<BoxCollider2D>();
|
||||
var prevColliderSize = box.size;
|
||||
prevColliderSize.x *= 4.97f;
|
||||
box.size = prevColliderSize;
|
||||
|
||||
var rend = stringOption.transform.GetChild(5).GetComponent<SpriteRenderer>();
|
||||
toggle.OverrideOnMouseOverListeners(() => rend.color = Buttons.Count > 0 ? Color.white : MiraAssets.AcceptedTeal);
|
||||
toggle.OverrideOnMouseOutListeners(() => rend.color = Buttons.Count > 0 ? MiraAssets.AcceptedTeal : Color.white);
|
||||
|
||||
// SetUpFromData method doesn't work correctly so we must set the values manually
|
||||
stringOption.Title = StringName;
|
||||
stringOption.Values = (Data as MultiSelectGameSetting)?.EnumNames?.Values?.ToArray() ?? new Il2CppStructArray<StringNames>(0);
|
||||
stringOption.Value = Convert.ToInt32(Value, NumberFormatInfo.InvariantInfo);
|
||||
|
||||
OptionBehaviour = stringOption;
|
||||
|
||||
return stringOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeGameSetting()
|
||||
{
|
||||
var stringOption = OptionBehaviour as StringOption;
|
||||
|
||||
// The value box itself becomes the button
|
||||
stringOption!.PlusBtn.gameObject.SetActive(false);
|
||||
stringOption.MinusBtn.gameObject.SetActive(false);
|
||||
|
||||
var background = stringOption.transform.GetChild(0);
|
||||
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||
background.localScale += new Vector3(1f, 0f, 0f);
|
||||
|
||||
var title = stringOption.TitleText;
|
||||
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||
title.fontSize = 2.9f; // Why is it different for string options??
|
||||
|
||||
var valueBox = stringOption.transform.GetChild(5);
|
||||
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||
}
|
||||
|
||||
private void ToggleButtons()
|
||||
{
|
||||
if (Buttons.Count > 0)
|
||||
{
|
||||
Buttons.ForEach(x => x.gameObject.Destroy());
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implement this
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
return Convert.ToSingle(Value, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override NetData GetNetData()
|
||||
{
|
||||
return new NetData(Id, EnumSerializer.EnumToBytes(Value));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleNetData(byte[] data)
|
||||
{
|
||||
SetValue(EnumSerializer.EnumFromBytes<T>(data, 0));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override T GetValueFromOptionBehaviour(OptionBehaviour optionBehaviour)
|
||||
{
|
||||
return Enum.Parse<T>(optionBehaviour.GetInt().ToString(NumberFormatInfo.InvariantInfo));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnValueChanged(T newValue)
|
||||
{
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(StringName, Data!.GetValueString(Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo)), false);
|
||||
if (!OptionBehaviour)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (OptionBehaviour is StringOption opt)
|
||||
{
|
||||
opt.Value = Convert.ToInt32(newValue, NumberFormatInfo.InvariantInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,6 +37,11 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// </summary>
|
||||
public bool ZeroInfinity { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether holding shift will divide the increment.
|
||||
/// </summary>
|
||||
public bool ShiftIncrement { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedNumberOption"/> class.
|
||||
/// </summary>
|
||||
@@ -49,6 +54,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
/// <param name="formatString">Optional format string for the option screen.</param>
|
||||
/// <param name="zeroInfinity">Whether zero is infinity or not.</param>
|
||||
/// <param name="includeInPreset">Whether to include this option in the preset or not.</param>
|
||||
/// <param name="shiftIncrement">Whether holding shift will divide the increment or not.</param>
|
||||
public ModdedNumberOption(
|
||||
string title,
|
||||
float defaultValue,
|
||||
@@ -65,6 +71,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
Increment = increment;
|
||||
SuffixType = suffixType;
|
||||
ZeroInfinity = zeroInfinity;
|
||||
ShiftIncrement = increment != 1f;
|
||||
|
||||
Value = Mathf.Clamp(defaultValue, min, max);
|
||||
|
||||
@@ -97,6 +104,12 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
Transform container)
|
||||
{
|
||||
var numberOption = Object.Instantiate(numberOpt, Vector3.zero, Quaternion.identity, container);
|
||||
numberOption.name =
|
||||
$"{ParentMod!.OptionsTitleText}.NumberOption.{TranslationController.Instance.GetString(StringName)}";
|
||||
var optionComponent = numberOption.gameObject.AddComponent<MiraNumberOptionComponent>();
|
||||
optionComponent.NumberOption = this;
|
||||
optionComponent.DefaultIncrement = Increment;
|
||||
optionComponent.ShiftIncrementToggle = ShiftIncrement;
|
||||
|
||||
numberOption.SetUpFromData(Data, 20);
|
||||
numberOption.OnValueChanged = (Il2CppSystem.Action<OptionBehaviour>)ValueChanged;
|
||||
@@ -115,6 +128,27 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
return numberOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeGameSetting()
|
||||
{
|
||||
var numberOption = OptionBehaviour as NumberOption;
|
||||
|
||||
numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||
numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||
|
||||
var background = numberOption.transform.GetChild(0);
|
||||
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||
background.localScale += new Vector3(1f, 0f, 0f);
|
||||
|
||||
var title = numberOption.TitleText;
|
||||
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||
|
||||
var valueBox = numberOption.transform.GetChild(5);
|
||||
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
@@ -145,7 +179,7 @@ public class ModdedNumberOption : ModdedOption<float>
|
||||
Value = Mathf.Clamp(newValue, Min, Max);
|
||||
HudManager.Instance.Notifier.AddSettingsChangeMessage(
|
||||
StringName,
|
||||
Data.GetValueString(Value),
|
||||
Data!.GetValueString(Value),
|
||||
false);
|
||||
|
||||
if (OptionBehaviour is NumberOption opt)
|
||||
|
||||
@@ -125,7 +125,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
{
|
||||
if (ConfigDefinition is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
|
||||
Error($"Attempted to save {Title} to preset, but ConfigDefinition is null.");
|
||||
return;
|
||||
}
|
||||
Bind(presetConfig);
|
||||
@@ -147,7 +147,7 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
|
||||
Error($"Attempted to load {Title} from preset, but ConfigDefinition is not found in preset.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,6 +181,23 @@ public abstract class ModdedOption<T> : IModdedOption
|
||||
PlayerOption playerOpt,
|
||||
Transform container);
|
||||
|
||||
/// <summary>
|
||||
/// Visually adjust the game option as needed.
|
||||
/// </summary>
|
||||
public abstract void ChangeGameSetting();
|
||||
|
||||
/// <summary>
|
||||
/// Binds the option to the configuration file of the parent mod, if available.
|
||||
/// </summary>
|
||||
public virtual void BindConfig()
|
||||
{
|
||||
var entry = ParentMod?.GetConfigFile().Bind(ConfigDefinition, DefaultValue);
|
||||
if (entry != null)
|
||||
{
|
||||
Value = entry.Value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implicitly converts the option to type of <typeparamref name="T"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -28,6 +28,27 @@ public class ModdedPlayerOption : ModdedOption<int>
|
||||
/// </summary>
|
||||
public bool AllowNone { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeGameSetting()
|
||||
{
|
||||
var numberOption = OptionBehaviour as PlayerOption;
|
||||
|
||||
numberOption!.PlusBtn.transform.localPosition += new Vector3(0.6f, 0f, 0f);
|
||||
numberOption.MinusBtn.transform.localPosition += new Vector3(1.5f, 0f, 0f);
|
||||
|
||||
var background = numberOption.transform.GetChild(0);
|
||||
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||
background.localScale += new Vector3(1f, 0f, 0f);
|
||||
|
||||
var title = numberOption.TitleText;
|
||||
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||
|
||||
var valueBox = numberOption.transform.GetChild(5);
|
||||
valueBox.localPosition += new Vector3(1.05f, 0f, 0f);
|
||||
valueBox.localScale += new Vector3(0.2f, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ModdedPlayerOption"/> class.
|
||||
/// </summary>
|
||||
@@ -55,6 +76,8 @@ public class ModdedPlayerOption : ModdedOption<int>
|
||||
Transform container)
|
||||
{
|
||||
var playerOption = Object.Instantiate(playerOpt, container);
|
||||
playerOption.name =
|
||||
$"{ParentMod!.OptionsTitleText}.PlayerOption.{TranslationController.Instance.GetString(StringName)}";
|
||||
|
||||
var filteredList = GetFilteredPlayers();
|
||||
Values = filteredList.ToIl2CppList();
|
||||
|
||||
@@ -29,6 +29,8 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
public override OptionBehaviour CreateOption(ToggleOption toggleOpt, NumberOption numberOpt, StringOption stringOpt, PlayerOption playerOpt, Transform container)
|
||||
{
|
||||
var toggleOption = Object.Instantiate(toggleOpt, Vector3.zero, Quaternion.identity, container);
|
||||
toggleOption.name =
|
||||
$"{ParentMod!.OptionsTitleText}.ToggleOption.{TranslationController.Instance.GetString(StringName)}";
|
||||
|
||||
toggleOption.SetUpFromData(Data, 20);
|
||||
|
||||
@@ -42,6 +44,21 @@ public class ModdedToggleOption : ModdedOption<bool>
|
||||
return toggleOption;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ChangeGameSetting()
|
||||
{
|
||||
var toggleOption = OptionBehaviour as ToggleOption;
|
||||
toggleOption!.transform.GetChild(1).localPosition += new Vector3(2.2f, 0f, 0f); // Checkbox
|
||||
|
||||
var title = toggleOption.TitleText;
|
||||
title.transform.localPosition = new(-2.0466f, 0f, -2.9968f);
|
||||
title.GetComponent<RectTransform>().sizeDelta = new(5.8f, 0.458f);
|
||||
|
||||
var background = toggleOption.transform.GetChild(2);
|
||||
background.localPosition += new Vector3(-0.8f, 0f, 0f);
|
||||
background.localScale += new Vector3(1f, 0f, 0f);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override float GetFloatData()
|
||||
{
|
||||
|
||||
@@ -8,6 +8,7 @@ using MiraAPI.Patches;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
@@ -75,7 +76,7 @@ public abstract class CustomActionButton
|
||||
/// <summary>
|
||||
/// Gets the keybind for this button. If null, no keybind will be added.
|
||||
/// </summary>
|
||||
public virtual MiraKeybind? Keybind => null;
|
||||
public virtual BaseKeybind? Keybind => null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the button's text outline color.
|
||||
@@ -216,6 +217,7 @@ public abstract class CustomActionButton
|
||||
new Vector3(MaxUses <= 0 ? -0.4f : 0.4f, 0.45f, -9f)
|
||||
);
|
||||
KeybindText = KeybindIcon.transform.GetChild(0).GetComponent<TextMeshPro>();
|
||||
HudManagerPatches.moddedKeybindIcons.Add(KeybindText);
|
||||
Button.usesRemainingSprite.transform.localPosition = new(-0.341f, 0.45f, -0.1f);
|
||||
}
|
||||
}
|
||||
@@ -232,7 +234,7 @@ public abstract class CustomActionButton
|
||||
return;
|
||||
}
|
||||
|
||||
this.Location = location;
|
||||
Location = location;
|
||||
|
||||
if (!moveButton)
|
||||
{
|
||||
@@ -493,8 +495,9 @@ public abstract class CustomActionButton
|
||||
if (Keybind != null && KeybindText != null)
|
||||
{
|
||||
KeybindText.text = Keybind.CurrentKey.ToString();
|
||||
KeybindIcon?.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value);
|
||||
KeybindIcon?.SetActive(ActiveInputManager.currentControlType is ActiveInputManager.InputType.Keyboard &&
|
||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value &&
|
||||
Keybind.CurrentKey != KeyboardKeyCode.None);
|
||||
}
|
||||
|
||||
if (Timer >= 0)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Base keybind class.
|
||||
/// </summary>
|
||||
public class BaseKeybind
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the Rewired <see cref="InputAction"/> assinged for this keybind.
|
||||
/// </summary>
|
||||
public InputAction? RewiredInputAction { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this keybind. Used in Rewired.
|
||||
/// </summary>
|
||||
public string Id { get; protected set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently assigned keycode.
|
||||
/// </summary>
|
||||
public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this);
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler of the keybind. Invoked when the keybind is activated.
|
||||
/// </summary>
|
||||
protected Action Handler { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BaseKeybind"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The unique identifier for this keybind.</param>
|
||||
public BaseKeybind(string id)
|
||||
{
|
||||
Id = id;
|
||||
Handler = () => { };
|
||||
}
|
||||
|
||||
public BaseKeybind()
|
||||
{
|
||||
Handler = () => { };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the keybind, invoking its handler.
|
||||
/// </summary>
|
||||
public void Invoke()
|
||||
{
|
||||
Handler.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="Action"/> that will be invoked when the keybind is used.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to add.</param>
|
||||
public void OnActivate(Action action)
|
||||
{
|
||||
Handler += action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an <see cref="Action"/> that would be invoked when the keybind is used.
|
||||
/// Not recommended to use - add checks in the method added in <see cref="OnActivate"/> itself rather than removing it from the keybind.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to remove.</param>
|
||||
public void RemoveOnActivate(Action action)
|
||||
{
|
||||
Handler -= action;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
@@ -17,16 +18,19 @@ public static class KeybindManager
|
||||
/// </summary>
|
||||
public static List<MiraKeybind> Keybinds { get; } = new();
|
||||
|
||||
[HideFromIl2Cpp]
|
||||
internal static Dictionary<Type, VanillaKeybind> VanillaKeybinds { get; set; } = [];
|
||||
|
||||
internal static void RewiredInit()
|
||||
{
|
||||
try
|
||||
{
|
||||
var instance = KeybindUtils.RewiredInputManager!;
|
||||
foreach (var keybind in KeybindManager.Keybinds)
|
||||
foreach (var keybind in Keybinds)
|
||||
{
|
||||
if (instance.userData.actions.ToArray().Any(x => x.name == keybind.Id))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Keybind of id {keybind.Id} already exists. Skipping it");
|
||||
Warning($"Keybind of id {keybind.Id} already exists. Skipping it");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -40,7 +44,7 @@ public static class KeybindManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while registering keybinds in Rewired: {e}");
|
||||
Error($"Error while registering keybinds in Rewired: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ public static class KeybindUtils
|
||||
/// </summary>
|
||||
/// <param name="keybind">The keybind to get the keycode from.</param>
|
||||
/// <returns>The currently assigned keycode.</returns>
|
||||
public static KeyboardKeyCode? GetKeycodeByKeybind(MiraKeybind keybind)
|
||||
public static KeyboardKeyCode GetKeycodeByKeybind(BaseKeybind keybind)
|
||||
{
|
||||
if (keybind.RewiredInputAction == null)
|
||||
{
|
||||
return null;
|
||||
return KeyboardKeyCode.None;
|
||||
}
|
||||
|
||||
return GetKeycodeByActionId(keybind.RewiredInputAction.id);
|
||||
@@ -34,10 +34,29 @@ public static class KeybindUtils
|
||||
/// <param name="actionId">The action ID.</param>
|
||||
/// <returns>The keyboard keycode.</returns>
|
||||
public static KeyboardKeyCode GetKeycodeByActionId(int actionId)
|
||||
{
|
||||
return GetActionElementMap(actionId)?.keyboardKeyCode ?? KeyboardKeyCode.None;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a <see cref="ActionElementMap"/> by id with ReInput.
|
||||
/// </summary>
|
||||
/// <param name="actionId">The action ID.</param>
|
||||
/// <returns>The keyboard keycode.</returns>
|
||||
public static ActionElementMap? GetActionElementMap(int actionId)
|
||||
{
|
||||
var player = ReInput.players.GetPlayer(0);
|
||||
return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false)?
|
||||
.keyboardKeyCode ?? KeyboardKeyCode.None;
|
||||
return player.controllers.maps.GetFirstElementMapWithAction(ControllerType.Keyboard, actionId, false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an <see cref="InputAction"/> by id.
|
||||
/// </summary>
|
||||
/// <param name="id">The action identifier name.</param>
|
||||
/// <returns>The keyboard keycode.</returns>
|
||||
public static InputAction? GetInputActionById(int id)
|
||||
{
|
||||
return RewiredInputManager?.userData?.GetActionById(id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using Reactor.Utilities;
|
||||
using System.Globalization;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
@@ -8,48 +6,28 @@ namespace MiraAPI.Keybinds;
|
||||
/// <summary>
|
||||
/// Represents a registered keybind.
|
||||
/// </summary>
|
||||
public class MiraKeybind
|
||||
public class MiraKeybind : BaseKeybind
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets name of the keybind
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the unique identifier for this keybind. Used in Rewired.
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the default keycode of this keybind.
|
||||
/// </summary>
|
||||
public KeyboardKeyCode DefaultKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this keybind should be checked for conflicts with other exclusive keybinds using the same key.
|
||||
/// </summary>
|
||||
public bool Exclusive { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the modifier keys assinged to this keybind.
|
||||
/// Gets the modifier keys assigned to this keybind.
|
||||
/// Due to Rewired limitations, there can only be 3 modifier keys.
|
||||
/// </summary>
|
||||
public ModifierKey[] ModifierKeys { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Rewired <see cref="InputAction"/> assinged for this keybind.
|
||||
/// Gets a value indicating whether this keybind should be checked for conflicts with other exclusive keybinds using the same key.
|
||||
/// </summary>
|
||||
public InputAction? RewiredInputAction { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the currently assigned keycode.
|
||||
/// </summary>
|
||||
public KeyboardKeyCode CurrentKey => KeybindUtils.GetKeycodeByKeybind(this) ?? KeyboardKeyCode.None;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the handler of the keybind. Invoked when the keybind is activated.
|
||||
/// </summary>
|
||||
private Action Handler { get; set; }
|
||||
public bool Exclusive { get; }
|
||||
|
||||
internal string? SourcePluginName { get; set; }
|
||||
|
||||
@@ -64,14 +42,12 @@ public class MiraKeybind
|
||||
string name,
|
||||
KeyboardKeyCode? defaultKeycode,
|
||||
ModifierKey[]? modifierKeys = null,
|
||||
bool exclusive = true)
|
||||
bool exclusive = true) : base(name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_'))
|
||||
{
|
||||
Name = name;
|
||||
Id = name.ToLower(CultureInfo.InvariantCulture).Replace(' ', '_');
|
||||
DefaultKey = defaultKeycode ?? KeyboardKeyCode.None;
|
||||
ModifierKeys = modifierKeys ?? Array.Empty<ModifierKey>();
|
||||
ModifierKeys = modifierKeys ?? [];
|
||||
Exclusive = exclusive;
|
||||
Handler = () => { };
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
@@ -79,31 +55,4 @@ public class MiraKeybind
|
||||
{
|
||||
return $"{Name} ({Id})";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Triggers the keybind, invoking its handler.
|
||||
/// </summary>
|
||||
public void Invoke()
|
||||
{
|
||||
Handler.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds an <see cref="Action"/> that will be invoked when the keybind is used.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to add.</param>
|
||||
public void OnActivate(Action action)
|
||||
{
|
||||
Handler += action;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes an <see cref="Action"/> that would be invoked when the keybind is used.
|
||||
/// Not recommended to use - add checks in the method added in <see cref="OnActivate"/> itself rather than removing it from the keybind.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action"/> to remove.</param>
|
||||
public void RemoveOnActivate(Action action)
|
||||
{
|
||||
Handler -= action;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a vanilla keybind.
|
||||
/// </summary>
|
||||
public class VanillaKeybind : BaseKeybind
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the <see cref="ActionButton"/> this keybind is binded to.
|
||||
/// </summary>
|
||||
public ActionButton? Button { get; internal set; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="VanillaKeybind"/> class.
|
||||
/// </summary>
|
||||
/// <param name="id">The keybind id.</param>
|
||||
public VanillaKeybind(int id)
|
||||
{
|
||||
RewiredInputAction = KeybindUtils.GetInputActionById(id);
|
||||
Id = RewiredInputAction?.name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
namespace MiraAPI.Keybinds;
|
||||
|
||||
/// <summary>
|
||||
/// Singleton for getting a vanilla keybind by <see cref="ActionButton"/>.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">ActionButton type.</typeparam>
|
||||
public static class VanillaKeybinding<T> where T : ActionButton
|
||||
{
|
||||
private static VanillaKeybind? _instance;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the instance of the option group.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1000
|
||||
public static VanillaKeybind Instance => _instance ??= KeybindManager.VanillaKeybinds[typeof(T)];
|
||||
#pragma warning restore CA1000
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public class LocalEnumSettingAttribute(
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalEnumSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
public override LocalEnumSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalEnumSetting(tab, configEntryBase, configEntryBase.SettingType, _name, _description, names);
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class LocalNumberSettingAttribute(
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalNumberSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
public override LocalNumberSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalNumberSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), increment, suffixType, formatString);
|
||||
}
|
||||
|
||||
@@ -20,5 +20,5 @@ public abstract class LocalSettingAttribute(
|
||||
/// <param name="tab">Gets the tab where the setting is located.</param>
|
||||
/// <param name="configEntryBase">Gets the config entry the setting is attached to.</param>
|
||||
/// <returns>The created class.</returns>
|
||||
internal abstract ILocalSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase);
|
||||
public abstract ILocalSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase);
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class LocalSliderSettingAttribute(
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalSliderSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
public override LocalSliderSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalSliderSetting(tab, configEntryBase, _name, _description, new FloatRange(min, max), displayValue, suffixType, formatString, roundValue);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ public class LocalToggleSettingAttribute(
|
||||
private readonly string? _description = description;
|
||||
|
||||
/// <inheritdoc/>
|
||||
internal override LocalToggleSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
public override LocalToggleSetting CreateSetting(Type tab, ConfigEntryBase configEntryBase)
|
||||
{
|
||||
return new LocalToggleSetting(tab, configEntryBase, _name, _description);
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ public static class LocalSettingsManager
|
||||
|
||||
if (TypeToTab.ContainsKey(type))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Local settings tab {type.Name} already exists.");
|
||||
Error($"Local settings tab {type.Name} already exists.");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ public abstract class LocalSettingsTab(ConfigFile config)
|
||||
{
|
||||
CreateLabel(generalLabel, Scroller.Inner, pair.Key, ref contentOffset);
|
||||
contentOrder = 1;
|
||||
contentIndex = 1;
|
||||
}
|
||||
|
||||
foreach (var setting in pair.Value)
|
||||
@@ -245,6 +246,7 @@ public abstract class LocalSettingsTab(ConfigFile config)
|
||||
tabButtonText.maxVisibleCharacters = int.MaxValue;
|
||||
tabButtonText.text = TabName;
|
||||
tabButtonText.alignment = TextAlignmentOptions.Left;
|
||||
tabButtonRend?.gameObject.SetActive(!TabAppearance.HideIconOnHover);
|
||||
}));
|
||||
tabButton.OnMouseOut.AddListener((UnityAction)(() =>
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using UnityEngine;
|
||||
namespace MiraAPI.LocalSettings;
|
||||
|
||||
/// <summary>
|
||||
/// Struct for modyfing tab apparance.
|
||||
/// Struct for modifying tab appearance.
|
||||
/// Defines how the settings look.
|
||||
/// </summary>
|
||||
public record struct LocalSettingTabAppearance()
|
||||
@@ -24,6 +24,11 @@ public record struct LocalSettingTabAppearance()
|
||||
/// </summary>
|
||||
public Color TabButtonColor { get; set; } = Color.white;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the icon should be hidden when tab button is hovered.
|
||||
/// </summary>
|
||||
public bool HideIconOnHover { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets hover color of the tab button.
|
||||
/// </summary>
|
||||
@@ -78,7 +83,7 @@ public record struct LocalSettingTabAppearance()
|
||||
/// Gets or sets hover color of enum buttons.
|
||||
/// </summary>
|
||||
public Color EnumHoverColor { get; set; } = Palette.AcceptedGreen;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets color of buttons.
|
||||
/// </summary>
|
||||
|
||||
@@ -20,7 +20,10 @@ public abstract class LocalSettingBase<T> : ILocalSetting
|
||||
/// <inheritdoc />
|
||||
public ConfigEntryBase ConfigEntry { get; }
|
||||
|
||||
internal LocalSettingsTab? Tab => LocalSettingsManager.Tabs.FirstOrDefault(x => x.Settings.Contains(this));
|
||||
/// <summary>
|
||||
/// Gets the tab of the local setting.
|
||||
/// </summary>
|
||||
public LocalSettingsTab? Tab => LocalSettingsManager.Tabs.FirstOrDefault(x => x.Settings.Contains(this));
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="LocalSettingBase{T}"/> class.
|
||||
@@ -53,7 +56,7 @@ public abstract class LocalSettingBase<T> : ILocalSetting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the value of the config entry, casted to the setting type.
|
||||
/// Gets the value of the config entry, cast to the setting type.
|
||||
/// </summary>
|
||||
/// <returns>The value.</returns>
|
||||
public virtual T GetValue()
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BepInEx;
|
||||
global using static Reactor.Utilities.Logger<MiraAPI.MiraApiPlugin>;
|
||||
using BepInEx;
|
||||
using BepInEx.Configuration;
|
||||
using BepInEx.Unity.IL2CPP;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.PluginLoading;
|
||||
@@ -30,6 +32,7 @@ public partial class MiraApiPlugin : BasePlugin
|
||||
/// </summary>
|
||||
public static Color DefaultHeaderColor { get; } = new Color32(77, 77, 77, 255);
|
||||
|
||||
internal static ConfigEntry<string>? DistanceSuffix { get; private set; }
|
||||
private static MiraPluginManager? PluginManager { get; set; }
|
||||
internal Harmony Harmony { get; } = new(Id);
|
||||
|
||||
@@ -40,6 +43,8 @@ public partial class MiraApiPlugin : BasePlugin
|
||||
|
||||
ReactorCredits.Register("Mira API", Version, true, ReactorCredits.AlwaysShow);
|
||||
|
||||
DistanceSuffix = Config.Bind("Display", "Distance", "unit(s)", "Dictates how a distance is shown in numeric option displays");
|
||||
|
||||
PluginManager = new MiraPluginManager();
|
||||
PluginManager.Initialize();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
{
|
||||
TabButtonHoverColor = MiraApiPlugin.MiraColor,
|
||||
TabIcon = MiraAssets.SettingsIcon,
|
||||
HideIconOnHover = false,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -27,9 +28,11 @@ public class MiraApiSettings(ConfigFile config) : LocalSettingsTab(config)
|
||||
public ConfigEntry<bool> ModifiersHudLeftSide { get; private set; } = config.Bind("Displays", "Show Modifiers HUD on Left Side", false);
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to show keybinds in the control mapper.
|
||||
/// Gets whether to show keybinds on buttons.
|
||||
/// </summary>
|
||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds in Control Mapper", true);
|
||||
[LocalToggleSetting]
|
||||
public ConfigEntry<bool> ShowKeybinds { get; private set; } = config.Bind("Keybinds", "Show Keybinds on Buttons", true);
|
||||
// This would be placed in the keybinds menu, but it crashes for Epic Games users. - Atony
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to apply cosmetic changes to the TaskAdder.
|
||||
|
||||
@@ -47,7 +47,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
_player = GetComponent<PlayerControl>();
|
||||
if (_player == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("ModifierComponent could not find PlayerControl on the same GameObject.");
|
||||
Error("ModifierComponent could not find PlayerControl on the same GameObject.");
|
||||
Destroy(this);
|
||||
return;
|
||||
}
|
||||
@@ -77,7 +77,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while deactivating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
Modifiers.Remove(modifier);
|
||||
@@ -95,7 +95,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while activating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
|
||||
if (modifier is TimedModifier { AutoStart: true } timer)
|
||||
@@ -122,7 +122,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while (fixed) updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
Error($"Error while updating modifier {modifier.ModifierName}: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -336,7 +336,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
var modifier = modifiers.FirstOrDefault();
|
||||
if (modifier is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {type.Name} because it is not active.");
|
||||
Error($"Cannot remove modifier {type.Name} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -352,7 +352,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
{
|
||||
if (!ActiveModifiers.Contains(modifier))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
|
||||
Error($"Cannot remove modifier {modifier.ModifierName} because it is not active on this player.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -382,7 +382,7 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
var modifier = GetModifier(uniqueId);
|
||||
if (modifier == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
|
||||
Error($"Cannot remove modifier with unique id {uniqueId} because it is not active.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -412,19 +412,19 @@ public class ModifierComponent(IntPtr cppPtr) : MonoBehaviour(cppPtr)
|
||||
// TODO: Make a proper synchronization system.
|
||||
if (LobbyBehaviour.Instance)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Modifiers added in the lobby won't sync to new players!");
|
||||
Warning($"Modifiers added in the lobby won't sync to new players!");
|
||||
}
|
||||
|
||||
var id = modifier.TypeId;
|
||||
if (modifier.Unique && ActiveModifiers.Find(x => x.TypeId == id) != null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player already has modifier with id {id}!");
|
||||
Error($"Player already has modifier with id {id}!");
|
||||
return null;
|
||||
}
|
||||
|
||||
if (ActiveModifiers.Contains(modifier))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player already has this modifier!");
|
||||
Error($"Player already has this modifier!");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ public static class ModifierExtensions
|
||||
var modifier = target.GetModifier(typeId, predicate);
|
||||
if (modifier is null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
|
||||
Error($"Player {target.PlayerId} does not have modifier with type ID {typeId}.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ public static class ModifierManager
|
||||
|
||||
if (modifierType.GetConstructor(Type.EmptyTypes) == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Game Modifier {modifierType.FullName} does not have a parameterless constructor!");
|
||||
Error($"Game Modifier {modifierType.FullName} does not have a parameterless constructor!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@ public static class ModifierManager
|
||||
var validPlayers = plrs.Where(x => IsGameModifierValid(x, modifier, modifier.TypeId)).ToList();
|
||||
if (validPlayers.Count == 0)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"No valid players for modifier {modifier.ModifierName}");
|
||||
Warning($"No valid players for modifier {modifier.ModifierName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ public static class ModifierManager
|
||||
|
||||
if (candidates.Count == 0)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning(
|
||||
Warning(
|
||||
$"No available players for modifier {modifier.ModifierName} at assignment {i + 1}");
|
||||
break;
|
||||
}
|
||||
@@ -166,7 +166,7 @@ public static class ModifierManager
|
||||
var plr = candidates.Random();
|
||||
if (plr == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
|
||||
Warning($"Valid player for modifier {modifier.ModifierName} disappeared");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ public abstract class TimedModifier : BaseModifier
|
||||
{
|
||||
if (TimerActive)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Can't start a timer that has already been started.");
|
||||
Error("Can't start a timer that has already been started.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace MiraAPI.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// Denotes a failure in conversion of values.
|
||||
/// </summary>
|
||||
/// <param name="message">The message to log.</param>
|
||||
public class ConversionFailException(string message) : Exception(message);
|
||||
@@ -0,0 +1,19 @@
|
||||
// TODO: Remove later before completing the pr
|
||||
#pragma warning disable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime.Attributes;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using Reactor.Utilities.Attributes;
|
||||
|
||||
namespace MiraAPI.Modules;
|
||||
|
||||
[RegisterInIl2Cpp, Serializable]
|
||||
public class MultiSelectGameSetting : BaseGameSetting
|
||||
{
|
||||
public Int32OptionNames OptionName;
|
||||
public Dictionary<int, StringNames> EnumNames;
|
||||
|
||||
public override string GetValueString(float value) => TranslationController.Instance.GetString(EnumNames[(int)value]);
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Modules;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a collection of enum values that can be stored and manipulated as a comma-separated string.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The enum type that this collection will store.</typeparam>
|
||||
/// <param name="values">One or more enum values to initialize the collection with.</param>
|
||||
[Serializable]
|
||||
public struct MultiSelectValue<T>(params T[] values) : IEnumerable<T>, IEquatable<MultiSelectValue<T>>, IDisposable where T : struct, Enum
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets the underlying comma-separated string representation of enum values.
|
||||
/// </summary>
|
||||
public string Values
|
||||
{
|
||||
readonly get => Helpers.Join(',', _values);
|
||||
set => _values = [.. value.TrueSplit(',').Select(Enum.Parse<T>)];
|
||||
}
|
||||
private HashSet<T> _values = [.. values];
|
||||
|
||||
/// <summary>
|
||||
/// Gets the number of values contained within.
|
||||
/// </summary>
|
||||
public readonly int Count => _values.Count;
|
||||
|
||||
/// <summary>
|
||||
/// Adds a single enum value to the end of the collection.
|
||||
/// </summary>
|
||||
/// <param name="item">The enum value to add.</param>
|
||||
/// <returns><c>true</c> if successfully added; otherwise, <c>false</c>.</returns>
|
||||
public readonly bool Add(T item) => _values.Add(item);
|
||||
|
||||
/// <summary>
|
||||
/// Removes the first occurrence of the specified enum value.<br/>
|
||||
/// Returns <c>true</c> if the value was found and removed, <c>false</c> otherwise.
|
||||
/// </summary>
|
||||
/// <param name="item">The enum value to remove.</param>
|
||||
/// <returns><c>true</c> if successfully removed; otherwise, <c>false</c>.</returns>
|
||||
public readonly bool Remove(T item) => _values.Remove(item);
|
||||
|
||||
/// <summary>
|
||||
/// Adds multiple enum values to the end of the collection.
|
||||
/// </summary>
|
||||
/// <param name="items">The collection of enum values to add.</param>
|
||||
public readonly void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
foreach (var item in items)
|
||||
_values.Add(item);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes multiple enum values from the collection.
|
||||
/// </summary>
|
||||
/// <param name="items">The enum values to remove.</param>
|
||||
/// <returns>The number of items successfully removed.</returns>
|
||||
public readonly int RemoveRange(params T[] items) => RemoveRange((IEnumerable<T>)items);
|
||||
|
||||
/// <summary>
|
||||
/// Removes multiple enum values from the collection.
|
||||
/// </summary>
|
||||
/// <param name="items">The collection of enum values to remove.</param>
|
||||
/// <returns>The number of items successfully removed.</returns>
|
||||
public readonly int RemoveRange(IEnumerable<T> items) => items.Count(Remove);
|
||||
|
||||
/// <summary>
|
||||
/// Determines whether the collection contains the specified enum value.
|
||||
/// </summary>
|
||||
/// <param name="item">The enum value to locate.</param>
|
||||
/// <returns>true if the item is found in the collection; otherwise, false.</returns>
|
||||
public readonly bool Contains(T item) => _values.Contains(item);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all enum values that match the specified predicate condition.<br/>
|
||||
/// Iterates through the collection and removes each matching value.
|
||||
/// </summary>
|
||||
/// <param name="predicate">A function that defines the condition for removing items.</param>
|
||||
/// <returns>The number of items removed from the collection.</returns>
|
||||
public readonly int RemoveAll(Func<T, bool> predicate)
|
||||
{
|
||||
var count = 0;
|
||||
|
||||
foreach (var item in this)
|
||||
{
|
||||
if (predicate(item) && Remove(item))
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all enum values from the collection.
|
||||
/// </summary>
|
||||
public readonly void Clear() => _values.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Gets the first value in the collection.
|
||||
/// </summary>
|
||||
/// <returns>Returns the first value.</returns>
|
||||
public readonly T First() => _values.First();
|
||||
|
||||
/// <summary>
|
||||
/// Serializes the current instance to an array of bytes.
|
||||
/// </summary>
|
||||
/// <returns>An array of bytes representing the current instance.</returns>
|
||||
public readonly byte[] ToBytes() => EnumSerializer.EnumsToBytes(this);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the collection to its string representation.
|
||||
/// </summary>
|
||||
/// <returns>A comma-separated string of enum values.</returns>
|
||||
public readonly override string ToString() => Values;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public readonly override bool Equals(object? obj) => obj is MultiSelectValue<T> other && Equals(other);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public readonly bool Equals(MultiSelectValue<T> other) => _values.SetEquals(other._values);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public readonly override int GetHashCode() => Values.GetHashCode();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public readonly IEnumerator<T> GetEnumerator() => ((IEnumerable<T>)_values).GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
readonly IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
/// <inheritdoc/>
|
||||
public readonly void Dispose() => _values.Clear();
|
||||
|
||||
/// <summary>
|
||||
/// Creates an instance of <see cref="MultiSelectValue{T}"/> from the provided byte array.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The bytes to deserialize from.</param>
|
||||
/// <returns>An instance of <see cref="MultiSelectValue{T}"/>.</returns>
|
||||
#pragma warning disable CA1000 // Do not declare static members on generic types
|
||||
public static MultiSelectValue<T> FromBytes(byte[] bytes) => new([.. EnumSerializer.EnumsFromBytes<T>(bytes)]);
|
||||
#pragma warning restore CA1000 // Do not declare static members on generic types
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current instance to its string representation.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert.</param>
|
||||
public static implicit operator string(MultiSelectValue<T> value) => value.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current instance to its array representation of values.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert.</param>
|
||||
public static implicit operator T[](MultiSelectValue<T> value) => [.. value._values];
|
||||
|
||||
/// <summary>
|
||||
/// Converts the current instance to one singular value.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert.</param>
|
||||
/// <exception cref="ConversionFailException">There were either no values, or more than one value.</exception>
|
||||
#pragma warning disable S3877 // Exceptions should not be thrown from unexpected methods
|
||||
public static implicit operator T(MultiSelectValue<T> value) =>
|
||||
value.Count == 1
|
||||
? value._values.First()
|
||||
: throw new ConversionFailException($"Tried to convert multiple or no values ({value.Values}) to a singular one");
|
||||
#pragma warning restore S3877 // Exceptions should not be thrown from unexpected methods
|
||||
|
||||
/// <summary>
|
||||
/// Converts the value to an instance of MultiSelectValue.
|
||||
/// </summary>
|
||||
/// <param name="value">The value to convert.</param>
|
||||
public static implicit operator MultiSelectValue<T>(T value) => new(value);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the array of values to an instance of MultiSelectValue.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to convert.</param>
|
||||
public static implicit operator MultiSelectValue<T>(T[] values) => new(values);
|
||||
|
||||
/// <summary>
|
||||
/// Converts the value string to an instance of MultiSelectValue.
|
||||
/// </summary>
|
||||
/// <param name="values">The values to convert.</param>
|
||||
public static implicit operator MultiSelectValue<T>(string values) => new([.. values.TrueSplit(',').Select(Enum.Parse<T>)]);
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparison.
|
||||
/// </summary>
|
||||
/// <param name="left">Left.</param>
|
||||
/// <param name="right">Right.</param>
|
||||
public static bool operator ==(MultiSelectValue<T> left, MultiSelectValue<T> right) => left.Equals(right);
|
||||
|
||||
/// <summary>
|
||||
/// Inequality comparison.
|
||||
/// </summary>
|
||||
/// <param name="left">Left.</param>
|
||||
/// <param name="right">Right.</param>
|
||||
public static bool operator !=(MultiSelectValue<T> left, MultiSelectValue<T> right) => !left.Equals(right);
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public class CustomGameOverRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<
|
||||
{
|
||||
if (!gameOver.VerifyCondition(innerNetObject, [.. data.Winners]))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"Game over condition not met for {gameOver.GetType().Name}");
|
||||
Info($"Game over condition not met for {gameOver.GetType().Name}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class CustomGameOverRpc(MiraApiPlugin plugin, uint id) : PlayerCustomRpc<
|
||||
else
|
||||
{
|
||||
CustomGameOver.Instance = null;
|
||||
Logger<MiraApiPlugin>.Error($"Unknown game over reason: {data.Reason}");
|
||||
Error($"Unknown game over reason: {data.Reason}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using AmongUs.Data;
|
||||
using AmongUs.GameOptions;
|
||||
@@ -8,8 +9,10 @@ using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using Reactor.Networking.Attributes;
|
||||
using Reactor.Networking.Rpc;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Networking;
|
||||
|
||||
@@ -169,7 +172,14 @@ public static class CustomMurderRpc
|
||||
|
||||
if (showKillAnim)
|
||||
{
|
||||
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
|
||||
try
|
||||
{
|
||||
HudManager.Instance.KillOverlay.ShowKillAnimation(source.Data, data);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Error($"Error with kill animation: {e.ToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
target.cosmetics.SetNameMask(false);
|
||||
@@ -202,7 +212,7 @@ public static class CustomMurderRpc
|
||||
bool teleportMurderer = true)
|
||||
{
|
||||
var cam = Camera.main?.GetComponent<FollowerCamera>();
|
||||
var isParticipant = PlayerControl.LocalPlayer == source || PlayerControl.LocalPlayer == target;
|
||||
var isParticipant = source.AmOwner || target.AmOwner;
|
||||
var sourcePhys = source.MyPhysics;
|
||||
|
||||
if (teleportMurderer)
|
||||
@@ -222,7 +232,7 @@ public static class CustomMurderRpc
|
||||
|
||||
if (createDeadBody)
|
||||
{
|
||||
deadBody = Object.Instantiate(GameManager.Instance.deadBodyPrefab[0]);
|
||||
deadBody = Object.Instantiate(GameManager.Instance.GetDeadBody(source.Data.Role));
|
||||
deadBody.enabled = false;
|
||||
deadBody.ParentId = target.PlayerId;
|
||||
deadBody.bodyRenderers.ToList().ForEach(target.SetPlayerMaterialColors);
|
||||
@@ -233,6 +243,13 @@ public static class CustomMurderRpc
|
||||
deadBody.transform.position = vector;
|
||||
}
|
||||
|
||||
source.Data.Role.KillAnimSpecialSetup(deadBody, source, target);
|
||||
target.Data.Role.KillAnimSpecialSetup(deadBody, source, target);
|
||||
|
||||
// no idea if this causes bugs, but innersloth is brain-dead
|
||||
// I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF I HATE INNERSCUFF
|
||||
PlayerControl.LocalPlayer.Data.Role.KillAnimSpecialSetup(deadBody, source, target);
|
||||
|
||||
if (isParticipant)
|
||||
{
|
||||
if (cam != null)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppSystem.Collections.Generic;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
[HarmonyPatch(typeof(DetectiveNotesMinigame), nameof(DetectiveNotesMinigame.SetImpostorPopup))]
|
||||
public static class DetectiveNotesMinigamePatch
|
||||
{
|
||||
public static void Prefix(DetectiveNotesMinigame __instance, bool active)
|
||||
{
|
||||
__instance.impostorTypePopup.gameObject.SetActive(active);
|
||||
if (!active)
|
||||
{
|
||||
ControllerManager.Instance.CloseOverlayMenu(__instance.ImpostorOverlay);
|
||||
ControllerManager.Instance.SetUpSelectables(ControllerManager.Instance.CurrentUiState, __instance.ControllerSelectable[0], __instance.ControllerSelectable);
|
||||
ControllerManager.Instance.SetCurrentSelected(__instance.ControllerSelectable[2]);
|
||||
return;
|
||||
}
|
||||
if (__instance.impostorButton != null)
|
||||
{
|
||||
ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, __instance.impostorTypePopup, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false);
|
||||
return;
|
||||
}
|
||||
__instance.impostorButton = new List<GameObject>();
|
||||
var allRoles = DestroyableSingleton<RoleManager>.Instance.AllRoles;
|
||||
var customRoles = CustomRoleManager.CustomRoleBehaviours;
|
||||
foreach (var role in allRoles)
|
||||
{
|
||||
if (role.TeamType != RoleTeamTypes.Impostor || role.Role == RoleTypes.ImpostorGhost || customRoles.Contains(role) )
|
||||
continue;
|
||||
var gameObject = Object.Instantiate(__instance.impostorTypePrefab, __instance.impostorTypeParent);
|
||||
__instance.impostorButton.Add(gameObject);
|
||||
gameObject.GetComponent<DetectiveImpostorType>().Initialize(__instance, role);
|
||||
__instance.ImpostorIconControllerSelectables.Add(gameObject.GetComponent<PassiveButton>());
|
||||
}
|
||||
ControllerManager.Instance.OpenOverlayMenu(__instance.ImpostorOverlay, __instance.impostorTypePopup, __instance.ImpostorIconControllerSelectables[0], __instance.ImpostorIconControllerSelectables, false);
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,7 @@ namespace MiraAPI.Patches.Events;
|
||||
internal static class EndGamePatches
|
||||
{
|
||||
[HarmonyPatch(typeof(GameManager), nameof(GameManager.RpcEndGame))]
|
||||
public static class GameManagerPatches
|
||||
public static class GameManagerRpcEndGamePatch
|
||||
{
|
||||
public static bool Prefix(GameOverReason endReason)
|
||||
{
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
using HarmonyLib;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
[HarmonyPatch(typeof(TutorialManager._RunTutorial_d__3), nameof(TutorialManager._RunTutorial_d__3.MoveNext))]
|
||||
[HarmonyPatch]
|
||||
public static class FreeplayRoundStartPatch
|
||||
{
|
||||
public static void Postfix(TutorialManager._RunTutorial_d__3 __instance, ref bool __result)
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return Helpers.GetStateMachineMoveNext<TutorialManager>(nameof(TutorialManager.RunTutorial))!;
|
||||
}
|
||||
|
||||
public static void Postfix(ref bool __result)
|
||||
{
|
||||
if (!__result)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
/// <summary>
|
||||
/// Patch to invoke AfterMurderEvent after a kill animation is performed.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class KillAnimationMurderEventPatch
|
||||
{
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return Helpers.GetStateMachineMoveNext<KillAnimation>(nameof(KillAnimation.CoPerformKill))!;
|
||||
}
|
||||
|
||||
public static void Postfix(Il2CppObjectBase __instance)
|
||||
{
|
||||
var wrapper = new StateMachineWrapper<KillAnimation>(__instance);
|
||||
if (wrapper.GetState() != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var source = wrapper.GetParameter<PlayerControl>("source");
|
||||
var target = wrapper.GetParameter<PlayerControl>("target");
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(source, target, Helpers.GetBodyById(target.PlayerId));
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches.Events;
|
||||
|
||||
/// <summary>
|
||||
/// General patches for the KillAnimation class.
|
||||
/// </summary>
|
||||
[HarmonyPatch]
|
||||
public static class KillAnimationPatches
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to trigger the <see cref="AfterMurderEvent"/>.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(KillAnimation._CoPerformKill_d__2), nameof(KillAnimation._CoPerformKill_d__2.MoveNext))]
|
||||
public static void KillAnimPostfix(KillAnimation._CoPerformKill_d__2 __instance)
|
||||
{
|
||||
// Checking if state is -1 to wait for the end of coroutine.
|
||||
if (__instance.__1__state != -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var afterMurderEvent = new AfterMurderEvent(__instance.source, __instance.target, Helpers.GetBodyById(__instance.target.PlayerId));
|
||||
MiraEventManager.InvokeEvent(afterMurderEvent);
|
||||
}
|
||||
}
|
||||
@@ -41,7 +41,7 @@ public static class ReportBodyEventPatch
|
||||
var source = AmongUsClient.Instance.FindObjectByNetId<PlayerControl>(reportBodyMessage.rpcObjectNetId);
|
||||
if (source == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
|
||||
Error("Failed to find source player for RpcReportDeadBodyMessage with NetId: " + reportBodyMessage.rpcObjectNetId);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Freeplay;
|
||||
|
||||
|
||||
@@ -2,12 +2,15 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.LocalSettings;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.Modifiers.Types;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using UnityEngine;
|
||||
@@ -63,19 +66,21 @@ internal static class TaskAdderPatches
|
||||
__instance.RootFolderPrefab.GetComponent<PassiveButton>().ClickMask = collider;
|
||||
__instance.RootFolderPrefab.gameObject.SetActive(false);
|
||||
__instance.TaskParent = inner.transform;
|
||||
var crewLocale = TranslationController.Instance.GetString(StringNames.Crewmate);
|
||||
var impLocale = TranslationController.Instance.GetString(StringNames.Impostor);
|
||||
|
||||
var crewmateFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == "Crewmate")!;
|
||||
var impostorFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == "Impostor")!;
|
||||
var neutralFolder = __instance.CreateFolder("Neutral", __instance.Root, 2, Color.gray);
|
||||
var crewmateFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == crewLocale)!;
|
||||
var impostorFolder = __instance.Root.SubFolders.ToArray().FirstOrDefault(x => x.FolderName == impLocale)!;
|
||||
//var neutralFolder = __instance.CreateFolder("Neutral", __instance.Root, 2, Color.gray);
|
||||
var modifiersFolder = __instance.CreateFolder("Modifiers", __instance.Root, 0, Color.blue);
|
||||
|
||||
folders.Clear();
|
||||
folders.Add("Crewmate", crewmateFolder);
|
||||
folders.Add("Impostor", impostorFolder);
|
||||
folders.Add("Neutral", neutralFolder);
|
||||
folders.Add(crewmateFolder.FolderName, crewmateFolder);
|
||||
folders.Add(impostorFolder.FolderName, impostorFolder);
|
||||
//folders.Add("Neutrals", neutralFolder);
|
||||
folders.Add("Modifiers", modifiersFolder);
|
||||
|
||||
int folderIdx = 3;
|
||||
int folderIdx = 2;
|
||||
foreach (var plugin in MiraPluginManager.Instance.RegisteredPlugins)
|
||||
{
|
||||
var pluginFolders = new Dictionary<string, TaskFolder>();
|
||||
@@ -95,10 +100,9 @@ internal static class TaskAdderPatches
|
||||
if (!pluginFolders.TryGetValue(folderName, out var pluginFolder))
|
||||
{
|
||||
pluginFolder = __instance.CreateFolder(plugin.PluginInfo.Metadata.Name, teamFolder);
|
||||
pluginFolder.name = $"Roles:{folderName}:{plugin.PluginId}";
|
||||
pluginFolders.Add(folderName, teamFolder);
|
||||
}
|
||||
|
||||
pluginFolder.RoleChildren.Add(role);
|
||||
}
|
||||
|
||||
if (plugin.InternalModifiers.Exists(x => x.ShowInFreeplay))
|
||||
@@ -114,6 +118,93 @@ internal static class TaskAdderPatches
|
||||
__instance.GoToRoot();
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(TaskAdderGame.PopulateRoot))]
|
||||
private static bool PopulateRootPrefix(TaskAdderGame __instance, TaskAdderGame.FolderType folderType, TaskFolder rootFolder, Il2CppSystem.Collections.Generic.Dictionary<string, TaskFolder> folders, Il2CppReferenceArray<NormalPlayerTask> taskList)
|
||||
{
|
||||
if (folderType != TaskAdderGame.FolderType.Tasks)
|
||||
{
|
||||
if (folderType != TaskAdderGame.FolderType.Roles)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
TaskFolder crewFolder = folders["Crewmates"] =
|
||||
Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||
crewFolder.gameObject.SetActive(false);
|
||||
crewFolder.SetFolderColor(TaskFolder.FolderColor.Blue);
|
||||
TaskFolder impFolder = folders["Impostors"] =
|
||||
Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||
impFolder.gameObject.SetActive(false);
|
||||
impFolder.SetFolderColor(TaskFolder.FolderColor.Red);
|
||||
crewFolder.FolderName = TranslationController.Instance.GetString(StringNames.Crewmate);
|
||||
impFolder.FolderName = TranslationController.Instance.GetString(StringNames.Impostor);
|
||||
rootFolder.SubFolders.Insert(0, crewFolder);
|
||||
rootFolder.SubFolders.Insert(0, impFolder);
|
||||
Il2CppSystem.Collections.Generic.List<RoleBehaviour> impRoles = new();
|
||||
Il2CppSystem.Collections.Generic.List<RoleBehaviour> crewRoles = new();
|
||||
foreach (var role in RoleManager.Instance.AllRoles)
|
||||
{
|
||||
if (role.Role != RoleTypes.ImpostorGhost && role.Role != RoleTypes.CrewmateGhost && !role.IsCustomRole())
|
||||
{
|
||||
if (role.TeamType == RoleTeamTypes.Crewmate)
|
||||
{
|
||||
crewRoles.Add(role);
|
||||
}
|
||||
else
|
||||
{
|
||||
impRoles.Add(role);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
crewFolder.RoleChildren = crewRoles;
|
||||
impFolder.RoleChildren = impRoles;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (taskList == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (NormalPlayerTask normalPlayerTask in taskList)
|
||||
{
|
||||
SystemTypes systemTypes = normalPlayerTask.StartAt;
|
||||
if (normalPlayerTask is DivertPowerTask task)
|
||||
{
|
||||
systemTypes = task.TargetSystem;
|
||||
}
|
||||
|
||||
if (systemTypes == SystemTypes.LowerEngine)
|
||||
{
|
||||
systemTypes = SystemTypes.UpperEngine;
|
||||
}
|
||||
|
||||
if (!folders.TryGetValue(systemTypes.ToString(), out var taskFolder3))
|
||||
{
|
||||
taskFolder3 = folders[systemTypes.ToString()] = Object.Instantiate(__instance.RootFolderPrefab, __instance.transform);
|
||||
taskFolder3.SetFolderColor(TaskFolder.FolderColor.Tan);
|
||||
taskFolder3.gameObject.SetActive(false);
|
||||
if (systemTypes == SystemTypes.UpperEngine)
|
||||
{
|
||||
taskFolder3.FolderName = TranslationController.Instance.GetString(StringNames.Engines);
|
||||
}
|
||||
else
|
||||
{
|
||||
taskFolder3.FolderName = TranslationController.Instance.GetString(systemTypes);
|
||||
}
|
||||
|
||||
rootFolder.SubFolders.Add(taskFolder3);
|
||||
}
|
||||
|
||||
taskFolder3.TaskChildren.Add(normalPlayerTask);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private static TaskFolder CreateFolder(this TaskAdderGame instance, string name, TaskFolder parent, int idx = -1, Color? color = null)
|
||||
{
|
||||
var folder = Object.Instantiate(instance.RootFolderPrefab, instance.transform);
|
||||
@@ -256,13 +347,13 @@ internal static class TaskAdderPatches
|
||||
|
||||
var roleChildren = taskFolder.RoleChildren
|
||||
.ToArray()
|
||||
.Where(x => !(taskFolder.IsChildOf(__instance.Root) && x is ICustomRole))
|
||||
.Where(x => x is not ICustomRole)
|
||||
.ToArray();
|
||||
foreach (var role in roleChildren)
|
||||
{
|
||||
TaskAddButton roleAddButton = Object.Instantiate(__instance.RoleButton);
|
||||
roleAddButton.SafePositionWorld = __instance.SafePositionWorld;
|
||||
roleAddButton.Text.text = prettyEnabled ? role.NiceName : "Be_" + role.NiceName + ".exe";
|
||||
roleAddButton.Text.text = prettyEnabled ? role.GetRoleName() : "Be_" + role.GetRoleName() + ".exe";
|
||||
roleAddButton.Role = role;
|
||||
if (prettyEnabled)
|
||||
{
|
||||
@@ -271,21 +362,7 @@ internal static class TaskAdderPatches
|
||||
? MiraAssets.ImpostorFile.LoadAsset()
|
||||
: MiraAssets.CrewmateFile.LoadAsset();
|
||||
}
|
||||
if (role is ICustomRole custom)
|
||||
{
|
||||
if (prettyEnabled && custom.Team == ModdedRoleTeams.Custom) roleAddButton.FileImage.sprite = MiraAssets.CustomTeamFile.LoadAsset();
|
||||
var customColor = custom.IntroConfiguration?.IntroTeamColor ?? Color.gray;
|
||||
if (custom.Team is ModdedRoleTeams.Crewmate)
|
||||
{
|
||||
customColor = Palette.CrewmateBlue;
|
||||
}
|
||||
else if (custom.Team is ModdedRoleTeams.Impostor)
|
||||
{
|
||||
customColor = Palette.ImpostorRed;
|
||||
}
|
||||
roleAddButton.FileImage.color = customColor;
|
||||
roleAddButton.RolloverHandler.OutColor = customColor;
|
||||
}
|
||||
|
||||
__instance.AddFileAsChildCustom(roleAddButton, ref num, ref num2, ref num3);
|
||||
if (roleAddButton != null && roleAddButton.Button != null)
|
||||
{
|
||||
@@ -293,9 +370,79 @@ internal static class TaskAdderPatches
|
||||
}
|
||||
}
|
||||
|
||||
string[] split = [];
|
||||
try
|
||||
{
|
||||
split = taskFolder.name.Replace("(Clone)", string.Empty).Split(':');
|
||||
}
|
||||
catch
|
||||
{
|
||||
// I hate you
|
||||
}
|
||||
if (split is ["Roles", _, _] && folders.Any(x => taskFolder.IsChildOf(x.Value)))
|
||||
{
|
||||
var plugin = MiraPluginManager.GetPluginByGuid(split[2]);
|
||||
if (plugin != null)
|
||||
{
|
||||
foreach (var role in plugin.InternalRoles)
|
||||
{
|
||||
var roleBehaviour = role.Value;
|
||||
if (roleBehaviour.Role == RoleTypes.ImpostorGhost || roleBehaviour.Role == RoleTypes.CrewmateGhost ||
|
||||
roleBehaviour is ICustomRole { Configuration.ShowInFreeplay: false })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var crewLocale = TranslationController.Instance.GetString(StringNames.Crewmate);
|
||||
var impLocale = TranslationController.Instance.GetString(StringNames.Impostor);
|
||||
|
||||
var teamFolder = roleBehaviour.IsImpostor ? impLocale : crewLocale;
|
||||
if (roleBehaviour is ICustomRole cs)
|
||||
{
|
||||
teamFolder = cs.Configuration.FreeplayFolder;
|
||||
}
|
||||
if (split[1] != teamFolder)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var roleAddButton = Object.Instantiate(__instance.RoleButton);
|
||||
roleAddButton.MyTask = null;
|
||||
roleAddButton.SafePositionWorld = __instance.SafePositionWorld;
|
||||
roleAddButton.Text.text = prettyEnabled ? roleBehaviour.GetRoleName() : "Be_" + roleBehaviour.GetRoleName() + ".exe";
|
||||
roleAddButton.Text.EnableMasking();
|
||||
roleAddButton.role = roleBehaviour;
|
||||
if (prettyEnabled)
|
||||
{
|
||||
roleAddButton.Text.fontSizeMin = 1;
|
||||
roleAddButton.FileImage.sprite = roleBehaviour.IsImpostor
|
||||
? MiraAssets.ImpostorFile.LoadAsset()
|
||||
: MiraAssets.CrewmateFile.LoadAsset();
|
||||
}
|
||||
if (roleBehaviour is ICustomRole custom)
|
||||
{
|
||||
if (prettyEnabled && custom.Team == ModdedRoleTeams.Custom) roleAddButton.FileImage.sprite = MiraAssets.CustomTeamFile.LoadAsset();
|
||||
var customColor = custom.IntroConfiguration?.IntroTeamColor ?? Color.gray;
|
||||
if (custom.Team is ModdedRoleTeams.Crewmate)
|
||||
{
|
||||
customColor = Palette.CrewmateBlue;
|
||||
}
|
||||
else if (custom.Team is ModdedRoleTeams.Impostor)
|
||||
{
|
||||
customColor = Palette.ImpostorRed;
|
||||
}
|
||||
roleAddButton.FileImage.color = customColor;
|
||||
roleAddButton.RolloverHandler.OutColor = customColor;
|
||||
}
|
||||
|
||||
__instance.AddFileAsChildCustom(roleAddButton, ref num, ref num2, ref num3);
|
||||
ControllerManager.Instance.AddSelectableUiElement(roleAddButton.Button);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (folders.TryGetValue("Modifiers", out var modifiersFolder) && taskFolder.IsChildOf(modifiersFolder))
|
||||
{
|
||||
var plugin = MiraPluginManager.GetPluginByGuid(taskFolder.gameObject.name.Replace("(Clone)", string.Empty));
|
||||
var plugin = MiraPluginManager.GetPluginByGuid(taskFolder.name.Replace("(Clone)", string.Empty));
|
||||
if (plugin != null)
|
||||
{
|
||||
foreach (var modifier in plugin.InternalModifiers)
|
||||
@@ -343,7 +490,6 @@ internal static class TaskAdderPatches
|
||||
}));
|
||||
|
||||
__instance.AddFileAsChildCustom(taskAddButton, ref num, ref num2, ref num3);
|
||||
|
||||
ControllerManager.Instance.AddSelectableUiElement(taskAddButton.Button);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,11 @@ namespace MiraAPI.Patches.Hud;
|
||||
public static class ButtonResetPatches
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
ResetCooldowns();
|
||||
}
|
||||
|
||||
public static void ResetCooldowns()
|
||||
{
|
||||
foreach (var customActionButton in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
@@ -18,7 +23,7 @@ public static class ButtonResetPatches
|
||||
}
|
||||
catch (System.Exception ex)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
|
||||
Error($"Error resetting cooldown and effect for button {customActionButton.GetType().Name}: {ex}");
|
||||
}
|
||||
|
||||
if (customActionButton.UsesMode == ButtonUsesMode.PerRound)
|
||||
|
||||
@@ -27,6 +27,8 @@ public static class HudManagerPatches
|
||||
|
||||
private static Dictionary<TextMeshPro, int> vanillaKeybindIcons = new();
|
||||
|
||||
internal static List<TextMeshPro> moddedKeybindIcons = new();
|
||||
|
||||
/*
|
||||
/// <summary>
|
||||
/// Trigger hudstart on current custom gamemode
|
||||
@@ -73,6 +75,23 @@ public static class HudManagerPatches
|
||||
gridArrange.Alignment = GridArrange.StartAlign.Right;
|
||||
aspectPosition.Alignment = AspectPosition.EdgeAlignments.LeftBottom;
|
||||
|
||||
if (Constants.GetPlatformType() is Platforms.Android or Platforms.IPhone)
|
||||
{
|
||||
var fakeButton = Object.Instantiate(__instance.AbilityButton, BottomLeft.transform);
|
||||
foreach (var renderer in fakeButton.GetComponentsInChildren<Renderer>())
|
||||
{
|
||||
renderer.enabled = false;
|
||||
}
|
||||
|
||||
fakeButton.buttonLabelText.Destroy();
|
||||
fakeButton.cooldownTimerText.Destroy();
|
||||
fakeButton.usesRemainingText.Destroy();
|
||||
fakeButton.GetComponent<PassiveButton>().Destroy();
|
||||
fakeButton.ToggleVisible(true);
|
||||
fakeButton.Destroy();
|
||||
}
|
||||
moddedKeybindIcons = [];
|
||||
|
||||
foreach (var button in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
var location = button.Location switch
|
||||
@@ -93,13 +112,14 @@ public static class HudManagerPatches
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to create custom button {button.GetType().Name}: {e}");
|
||||
Error($"Failed to create custom button {button.GetType().Name}: {e}");
|
||||
}
|
||||
}
|
||||
__instance.ImpostorVentButton.transform.SetParent(null);
|
||||
__instance.ImpostorVentButton.transform.SetParent(BottomRight.transform);
|
||||
|
||||
gridArrange.Start();
|
||||
gridArrange.ArrangeChilds();
|
||||
|
||||
aspectPosition.AdjustPosition();
|
||||
|
||||
vanillaKeybindIcons = [];
|
||||
@@ -111,6 +131,7 @@ public static class HudManagerPatches
|
||||
{ __instance.ReportButton.gameObject, 7 },
|
||||
{ __instance.ImpostorVentButton.gameObject, 50 },
|
||||
{ __instance.SabotageButton.gameObject, 4 },
|
||||
{ __instance.AbilityButton.gameObject, 49 },
|
||||
};
|
||||
|
||||
foreach (var kvp in vanillaButtons)
|
||||
@@ -125,6 +146,8 @@ public static class HudManagerPatches
|
||||
}
|
||||
var icon = Helpers.CreateKeybindIcon(buttonObj, key, keybindIconPos);
|
||||
vanillaKeybindIcons.Add(icon.transform.GetChild(0).GetComponent<TextMeshPro>(), actionId);
|
||||
var comp = buttonObj.GetComponent<ActionButton>();
|
||||
KeybindManager.VanillaKeybinds[comp.GetType()].Button = comp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +176,7 @@ public static class HudManagerPatches
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to set custom button {button.GetType().Name} active: {e}");
|
||||
Error($"Failed to set custom button {button.GetType().Name} active: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,18 +185,22 @@ public static class HudManagerPatches
|
||||
[HarmonyPostfix]
|
||||
public static void UpdatePostfix()
|
||||
{
|
||||
var canSeeBinds = ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value;
|
||||
foreach (var btnIcon in vanillaKeybindIcons)
|
||||
{
|
||||
btnIcon.Key.text = KeybindUtils.GetKeycodeByActionId(btnIcon.Value).ToString();
|
||||
btnIcon.Key.transform.parent.gameObject.SetActive(ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard &&
|
||||
LocalSettingsTabSingleton<MiraApiSettings>.Instance.ShowKeybinds.Value);
|
||||
btnIcon.Key.transform.parent.gameObject.SetActive(canSeeBinds);
|
||||
}
|
||||
foreach (var btnIcon in moddedKeybindIcons)
|
||||
{
|
||||
btnIcon.transform.parent.gameObject.SetActive(canSeeBinds);
|
||||
}
|
||||
|
||||
var player = ReInput.players.GetPlayer(0);
|
||||
foreach (var entry in KeybindManager.Keybinds)
|
||||
{
|
||||
var player = ReInput.players.GetPlayer(0);
|
||||
var keyboard = player.controllers.Keyboard;
|
||||
|
||||
bool modifierKeysPressed = entry.ModifierKeys.All(k => keyboard.GetModifierKey(k)) ||
|
||||
entry.ModifierKeys.Length <= 0;
|
||||
if (player.GetButtonDown(entry.Id) && modifierKeysPressed)
|
||||
@@ -181,5 +208,10 @@ public static class HudManagerPatches
|
||||
entry.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var entry in KeybindManager.VanillaKeybinds.Values.Where(x => player.GetButtonDown(x.Id)))
|
||||
{
|
||||
entry.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using Reactor.Utilities;
|
||||
|
||||
namespace MiraAPI.Patches;
|
||||
|
||||
@@ -29,14 +33,29 @@ public static class IntroCutscenePatches
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(IntroCutscene._ShowRole_d__41), nameof(IntroCutscene._ShowRole_d__41.MoveNext))]
|
||||
[HarmonyPriority(Priority.Last)]
|
||||
public static void ShowRolePatch(IntroCutscene._ShowRole_d__41 __instance)
|
||||
[HarmonyPatch]
|
||||
public static class IntroCutsceneShowRolePatch
|
||||
{
|
||||
var realInstance = __instance.__4__this;
|
||||
var @event = new IntroRoleRevealEvent(realInstance);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
return Helpers.GetStateMachineMoveNext<IntroCutscene>(nameof(IntroCutscene.ShowRole))!;
|
||||
}
|
||||
|
||||
public static void Postfix(Il2CppObjectBase __instance)
|
||||
{
|
||||
var wrapper = new StateMachineWrapper<IntroCutscene>(__instance);
|
||||
// run before the first yield
|
||||
if (wrapper.GetState() != 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var introCutscene = wrapper.Instance;
|
||||
|
||||
Info("IntroCutscene ShowRole reached");
|
||||
var @event = new IntroRoleRevealEvent(introCutscene);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
@@ -66,16 +85,54 @@ public static class IntroCutscenePatches
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(IntroCutscene.OnDestroy))]
|
||||
public static void GameBeginPatch(IntroCutscene __instance)
|
||||
[HarmonyPatch]
|
||||
public static class IntroCutsceneDestroyPatch
|
||||
{
|
||||
MiraEventManager.InvokeEvent(new IntroEndEvent(__instance));
|
||||
private static bool _usedFallback;
|
||||
|
||||
var @event = new BeforeRoundStartEvent(true);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
public static MethodBase TargetMethod()
|
||||
{
|
||||
var onDestroy = AccessTools.Method(typeof(IntroCutscene), "OnDestroy");
|
||||
if (onDestroy != null)
|
||||
{
|
||||
_usedFallback = false;
|
||||
Info("Using OnDestroy for IntroCutsceneDestroyPatch");
|
||||
return onDestroy;
|
||||
}
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
_usedFallback = true;
|
||||
return Helpers.GetStateMachineMoveNext<IntroCutscene>(nameof(IntroCutscene.CoBegin))!;
|
||||
}
|
||||
|
||||
public static void Postfix(Il2CppObjectBase __instance)
|
||||
{
|
||||
IntroCutscene introCutscene;
|
||||
|
||||
if (_usedFallback)
|
||||
{
|
||||
var wrapper = new StateMachineWrapper<IntroCutscene>(__instance);
|
||||
// run after the final yield
|
||||
if (wrapper.GetState() != -1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
introCutscene = wrapper.Instance;
|
||||
}
|
||||
else
|
||||
{
|
||||
introCutscene = __instance.Cast<IntroCutscene>();
|
||||
}
|
||||
|
||||
|
||||
Info("IntroCutscene ended");
|
||||
|
||||
MiraEventManager.InvokeEvent(new IntroEndEvent(introCutscene));
|
||||
|
||||
var @event = new BeforeRoundStartEvent(true);
|
||||
MiraEventManager.InvokeEvent(@event);
|
||||
|
||||
if (@event.IsCancelled) return;
|
||||
MiraEventManager.InvokeEvent(new RoundStartEvent(true));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ namespace MiraAPI.Patches.Keybinds;
|
||||
[HarmonyPatch(typeof(ControlMapper))]
|
||||
public static class ControlMapperPatches
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
// Patching this crashes for Epic Games users
|
||||
/*[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(ControlMapper.Start))]
|
||||
private static void StartPrefix(ControlMapper __instance)
|
||||
{
|
||||
@@ -37,7 +38,7 @@ public static class ControlMapperPatches
|
||||
entry.Value = !entry.Value;
|
||||
text.text = $"Show Keybinds: {(entry.Value ? "On" : "Off")}";
|
||||
}));
|
||||
}
|
||||
}*/
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(ControlMapper.Update))]
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Keybinds;
|
||||
using MonoMod.Utils;
|
||||
using Rewired;
|
||||
|
||||
namespace MiraAPI.Patches.Keybinds;
|
||||
@@ -20,5 +21,16 @@ public static class KeybindMenuPatch
|
||||
KeybindUtils.RewiredInputManager = __instance;
|
||||
KeybindManager.RewiredInit();
|
||||
_registered = true;
|
||||
|
||||
// Set these here right after input manager initializes
|
||||
KeybindManager.VanillaKeybinds = new()
|
||||
{
|
||||
{ typeof(KillButton), new VanillaKeybind(8) },
|
||||
{ typeof(UseButton), new VanillaKeybind(6) },
|
||||
{ typeof(ReportButton), new VanillaKeybind(7) },
|
||||
{ typeof(VentButton), new VanillaKeybind(50) },
|
||||
{ typeof(SabotageButton), new VanillaKeybind(4) },
|
||||
{ typeof(AbilityButton), new VanillaKeybind(49) },
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public static class OptionsMenuPatches
|
||||
private static Dictionary<int, List<GameObject>> tabButtons = new();
|
||||
|
||||
/// <summary>
|
||||
/// Creates the tabs and their content
|
||||
/// Creates the tabs and their content.
|
||||
/// </summary>
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(nameof(OptionsMenuBehaviour.Start))]
|
||||
|
||||
@@ -8,7 +8,7 @@ public static class ToggleBehaviourPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// ResetText was inlined ofc.
|
||||
/// Skill issue
|
||||
/// Skill issue.
|
||||
/// </summary>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(ToggleButtonBehaviour.UpdateText))]
|
||||
|
||||
@@ -6,10 +6,13 @@ using System.Linq;
|
||||
using AmongUs.Data;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Utilities;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Reactor.Utilities.Extensions;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Menu;
|
||||
@@ -20,6 +23,20 @@ public static class HatsTabPatches
|
||||
private static SortedList<string, List<HatData>> sortedHats = [];
|
||||
private static int currentPage;
|
||||
|
||||
private static void PreviousPage(HatsTab hatsTab)
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? sortedHats.Count - 1 : currentPage;
|
||||
GenerateHats(hatsTab, currentPage);
|
||||
}
|
||||
|
||||
private static void NextPage(HatsTab hatsTab)
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > sortedHats.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(hatsTab, currentPage);
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(HatsTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(HatsTab __instance)
|
||||
@@ -38,6 +55,8 @@ public static class HatsTabPatches
|
||||
}
|
||||
}
|
||||
|
||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
||||
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
@@ -45,22 +64,17 @@ public static class HatsTabPatches
|
||||
|
||||
[HarmonyPatch(typeof(HatsTab), nameof(HatsTab.Update))]
|
||||
[HarmonyPrefix]
|
||||
|
||||
public static void UpdatePrefix(HatsTab __instance)
|
||||
{
|
||||
if (sortedHats.Count == 0) return;
|
||||
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? sortedHats.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
PreviousPage(__instance);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > sortedHats.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
NextPage(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,10 +98,10 @@ public static class HatsTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName} ({currentPage + 1}/{sortedHats.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{sortedHats.Count})";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSize = 5f;
|
||||
text.fontSizeMax = 5f;
|
||||
text.fontSizeMin = 0f;
|
||||
var xLerp = __instance.XRange.Lerp(0.5f);
|
||||
var yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
@@ -125,9 +139,17 @@ public static class HatsTabPatches
|
||||
{
|
||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
||||
colorChip.gameObject.name = hat.ProductId;
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectHat(hat)));
|
||||
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectHat(HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat))));
|
||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectHat(hat)));
|
||||
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectHat(HatManager.Instance.GetHatById(DataManager.Player.Customization.Hat))));
|
||||
}
|
||||
else
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectHat(hat)));
|
||||
}
|
||||
|
||||
colorChip.Inner.SetHat(hat, __instance.HasLocalPlayer() ? PlayerControl.LocalPlayer.Data.DefaultOutfit.ColorId : DataManager.Player.Customization.Color);
|
||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
||||
colorChip.SelectionHighlight.gameObject.SetActive(false);
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
namespace MiraAPI.Patches.Menu;
|
||||
|
||||
public static class InventoryUtility
|
||||
{
|
||||
public static void CreateNextBackButtons<T>(T tab, Action<T> previousPage, Action<T> nextPage) where T : InventoryTab
|
||||
{
|
||||
var title = tab.transform.FindChild("Text");
|
||||
title.localPosition = new Vector3(.9123f, -.23f, -55);
|
||||
title.GetComponent<TextMeshPro>().alignment = TextAlignmentOptions.Center;
|
||||
|
||||
tab.scroller.Hitbox.transform.localScale -= new Vector3(0, 0.1f, 0);
|
||||
tab.scroller.Hitbox.transform.localPosition -= new Vector3(0, 0.1f, 0);
|
||||
|
||||
var leftButtonElement = Object.Instantiate(PlayerCustomizationMenu.Instance.BackButton.gameObject, tab.transform);
|
||||
var leftSpriteRenderer = leftButtonElement.GetComponent<SpriteRenderer>();
|
||||
var leftButton = leftButtonElement.GetComponent<PassiveButton>();
|
||||
|
||||
leftButtonElement.transform.localPosition = new Vector3(-1.1f, -0.2f, -55);
|
||||
leftSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset();
|
||||
leftSpriteRenderer.flipX = true;
|
||||
|
||||
leftButton.OnMouseOver = new UnityEvent();
|
||||
leftButton.OnMouseOver.AddListener((Action)(() => leftSpriteRenderer.sprite = MiraAssets.NextButtonActive.LoadAsset()));
|
||||
leftButton.OnMouseOut = new UnityEvent();
|
||||
leftButton.OnMouseOut.AddListener((Action)(() => leftSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset()));
|
||||
|
||||
leftButton.OnClick = new Button.ButtonClickedEvent();
|
||||
leftButton.OnClick.AddListener((Action)(() => previousPage(tab)));
|
||||
|
||||
var rightButtonElement = Object.Instantiate(leftButtonElement, tab.transform);
|
||||
var rightSpriteRenderer = rightButtonElement.GetComponent<SpriteRenderer>();
|
||||
var rightButton = rightButtonElement.GetComponent<PassiveButton>();
|
||||
|
||||
rightButtonElement.transform.localPosition = new Vector3(2.8f, -0.2f, -55);
|
||||
rightSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset();
|
||||
rightSpriteRenderer.flipX = false;
|
||||
|
||||
rightButton.OnMouseOver = new UnityEvent();
|
||||
rightButton.OnMouseOver.AddListener((Action)(() => rightSpriteRenderer.sprite = MiraAssets.NextButtonActive.LoadAsset()));
|
||||
rightButton.OnMouseOut = new UnityEvent();
|
||||
rightButton.OnMouseOut.AddListener((Action)(() => rightSpriteRenderer.sprite = MiraAssets.NextButton.LoadAsset()));
|
||||
|
||||
rightButton.OnClick = new Button.ButtonClickedEvent();
|
||||
rightButton.OnClick.AddListener((Action)(() => nextPage(tab)));
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,20 @@ public static class NameplatesTabPatches
|
||||
}
|
||||
}
|
||||
|
||||
private static void PreviousPage(NameplatesTab tab)
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? SortedNameplates.Count - 1 : currentPage;
|
||||
GenerateHats(tab, currentPage);
|
||||
}
|
||||
|
||||
private static void NextPage(NameplatesTab tab)
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > SortedNameplates.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(tab, currentPage);
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(NameplatesTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(NameplatesTab __instance)
|
||||
@@ -33,6 +47,9 @@ public static class NameplatesTabPatches
|
||||
__instance.plateId = HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.namePlate).ProdId;
|
||||
|
||||
if (!SortedNameplates.ContainsKey("Vanilla")) AddRange(DestroyableSingleton<HatManager>.Instance.GetUnlockedNamePlates().Select(x => ("Vanilla", x)));
|
||||
|
||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
||||
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
@@ -45,15 +62,11 @@ public static class NameplatesTabPatches
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? SortedNameplates.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
PreviousPage(__instance);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > SortedNameplates.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
NextPage(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,10 +86,10 @@ public static class NameplatesTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName} ({currentPage + 1}/{SortedNameplates.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{SortedNameplates.Count})";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSize = 5f;
|
||||
text.fontSizeMax = 5f;
|
||||
text.fontSizeMin = 0f;
|
||||
float xLerp = __instance.XRange.Lerp(0.5f);
|
||||
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
@@ -99,9 +112,20 @@ public static class NameplatesTabPatches
|
||||
{
|
||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
||||
colorChip.gameObject.name = namePlate.ProductId;
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectNameplate(namePlate)));
|
||||
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectNameplate(HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.NamePlate))));
|
||||
|
||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectNameplate(namePlate)));
|
||||
colorChip.Button.OnMouseOut.AddListener(
|
||||
(Action)(() => __instance.SelectNameplate(
|
||||
HatManager.Instance.GetNamePlateById(DataManager.Player.Customization.NamePlate))));
|
||||
}
|
||||
else
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectNameplate(namePlate)));
|
||||
}
|
||||
|
||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
||||
colorChip.ProductId = namePlate.ProdId;
|
||||
|
||||
|
||||
@@ -18,6 +18,20 @@ public static class SkinsTabPatches
|
||||
private static SortedList<string, List<SkinData>> sortedSkins = [];
|
||||
private static int currentPage;
|
||||
|
||||
private static void PreviousPage(SkinsTab tab)
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? sortedSkins.Count - 1 : currentPage;
|
||||
GenerateHats(tab, currentPage);
|
||||
}
|
||||
|
||||
private static void NextPage(SkinsTab tab)
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > sortedSkins.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(tab, currentPage);
|
||||
}
|
||||
|
||||
[HarmonyPatch(nameof(SkinsTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(SkinsTab __instance)
|
||||
@@ -36,6 +50,8 @@ public static class SkinsTabPatches
|
||||
}
|
||||
}
|
||||
|
||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
||||
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
@@ -48,15 +64,11 @@ public static class SkinsTabPatches
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? sortedSkins.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
PreviousPage(__instance);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > sortedSkins.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
NextPage(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +88,10 @@ public static class SkinsTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName} ({currentPage + 1}/{sortedSkins.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{sortedSkins.Count})";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSize = 5f;
|
||||
text.fontSizeMax = 5f;
|
||||
text.fontSizeMin = 0f;
|
||||
float xLerp = __instance.XRange.Lerp(0.5f);
|
||||
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
@@ -102,9 +114,18 @@ public static class SkinsTabPatches
|
||||
{
|
||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
||||
colorChip.gameObject.name = skin.ProductId;
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectSkin(skin)));
|
||||
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectSkin(HatManager.Instance.GetSkinById(DataManager.Player.Customization.Skin))));
|
||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectSkin(skin)));
|
||||
colorChip.Button.OnMouseOut.AddListener(
|
||||
(Action)(() =>
|
||||
__instance.SelectSkin(HatManager.Instance.GetSkinById(DataManager.Player.Customization.Skin))));
|
||||
}
|
||||
else
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectSkin(skin)));
|
||||
}
|
||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
||||
colorChip.ProductId = skin.ProductId;
|
||||
colorChip.SelectionHighlight.gameObject.SetActive(false);
|
||||
|
||||
@@ -25,6 +25,21 @@ public static class VisorsTabPatches
|
||||
}
|
||||
}
|
||||
|
||||
private static void PreviousPage(VisorsTab tab)
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? SortedVisors.Count - 1 : currentPage;
|
||||
GenerateHats(tab, currentPage);
|
||||
}
|
||||
|
||||
private static void NextPage(VisorsTab tab)
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > SortedVisors.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(tab, currentPage);
|
||||
}
|
||||
|
||||
|
||||
[HarmonyPatch(nameof(VisorsTab.OnEnable))]
|
||||
[HarmonyPrefix]
|
||||
public static bool OnEnablePrefix(VisorsTab __instance)
|
||||
@@ -32,6 +47,9 @@ public static class VisorsTabPatches
|
||||
__instance.visorId = HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor).ProdId;
|
||||
|
||||
if (!SortedVisors.ContainsKey("Vanilla")) AddRange(DestroyableSingleton<HatManager>.Instance.GetUnlockedVisors().Select(x => ("Vanilla", x)));
|
||||
|
||||
InventoryUtility.CreateNextBackButtons(__instance, PreviousPage, NextPage);
|
||||
|
||||
GenerateHats(__instance, currentPage);
|
||||
|
||||
return false;
|
||||
@@ -44,15 +62,11 @@ public static class VisorsTabPatches
|
||||
{
|
||||
if (Input.GetKeyDown(KeyCode.LeftControl) || Input.GetKeyDown(KeyCode.RightControl) || Input.GetKeyDown(KeyCode.LeftArrow))
|
||||
{
|
||||
currentPage--;
|
||||
currentPage = currentPage < 0 ? SortedVisors.Count - 1 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
PreviousPage(__instance);
|
||||
}
|
||||
else if (Input.GetKeyDown(KeyCode.Tab) || Input.GetKeyDown(KeyCode.RightArrow))
|
||||
{
|
||||
currentPage++;
|
||||
currentPage = currentPage > SortedVisors.Count - 1 ? 0 : currentPage;
|
||||
GenerateHats(__instance, currentPage);
|
||||
NextPage(__instance);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,10 +86,10 @@ public static class VisorsTabPatches
|
||||
text.gameObject.transform.localScale = Vector3.one;
|
||||
text.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
text.EnableStencilMasking();
|
||||
text.text = $"{groupName} ({currentPage + 1}/{SortedVisors.Count})\nPress Ctrl or Tab to cycle pages";
|
||||
text.text = $"{groupName} ({currentPage + 1}/{SortedVisors.Count})";
|
||||
text.alignment = TextAlignmentOptions.Center;
|
||||
text.fontSize = 3f;
|
||||
text.fontSizeMax = 3f;
|
||||
text.fontSize = 5f;
|
||||
text.fontSizeMax = 5f;
|
||||
text.fontSizeMin = 0f;
|
||||
float xLerp = __instance.XRange.Lerp(0.5f);
|
||||
float yLerp = __instance.YStart - hatIndex / __instance.NumPerRow * __instance.YOffset;
|
||||
@@ -98,9 +112,20 @@ public static class VisorsTabPatches
|
||||
{
|
||||
var colorChip = Object.Instantiate(__instance.ColorTabPrefab, __instance.scroller.Inner);
|
||||
colorChip.gameObject.name = visor.ProductId;
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.ClickEquip()));
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectVisor(visor)));
|
||||
colorChip.Button.OnMouseOut.AddListener((Action)(() => __instance.SelectVisor(HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor))));
|
||||
|
||||
if (ActiveInputManager.currentControlType == ActiveInputManager.InputType.Keyboard)
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)__instance.ClickEquip);
|
||||
colorChip.Button.OnMouseOver.AddListener((Action)(() => __instance.SelectVisor(visor)));
|
||||
colorChip.Button.OnMouseOut.AddListener(
|
||||
(Action)(() =>
|
||||
__instance.SelectVisor(HatManager.Instance.GetVisorById(DataManager.Player.Customization.Visor))));
|
||||
}
|
||||
else
|
||||
{
|
||||
colorChip.Button.OnClick.AddListener((Action)(() => __instance.SelectVisor(visor)));
|
||||
}
|
||||
|
||||
colorChip.Button.ClickMask = __instance.scroller.Hitbox;
|
||||
colorChip.ProductId = visor.ProductId;
|
||||
colorChip.SelectionHighlight.gameObject.SetActive(false);
|
||||
|
||||
@@ -32,11 +32,20 @@ internal static class GameOptionsMenuPatch
|
||||
{
|
||||
ModifiersUpdate(ref num);
|
||||
}
|
||||
else if (__instance.name == "CUSTOM TAB 1")
|
||||
{
|
||||
CustomMenuOneUpdate(ref num);
|
||||
}
|
||||
else if (__instance.name == "CUSTOM TAB 2")
|
||||
{
|
||||
CustomMenuTwoUpdate(ref num);
|
||||
}
|
||||
else
|
||||
{
|
||||
var filteredGroups =
|
||||
GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
||||
.Where(x => x.OptionableType == null &&
|
||||
!x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
{
|
||||
@@ -99,6 +108,48 @@ internal static class GameOptionsMenuPatch
|
||||
}
|
||||
}
|
||||
|
||||
private static void CustomMenuOneUpdate(ref float num)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? [];
|
||||
|
||||
foreach (var modGroup in groups)
|
||||
{
|
||||
UpdateGroup(modGroup, ref num);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CustomMenuOneCreate(GameOptionsMenu menu)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.ParentMenu == MenuCategory.CustomOne) ?? [];
|
||||
foreach (var group in groups)
|
||||
{
|
||||
CreateGroup(menu, group);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CustomMenuTwoUpdate(ref float num)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? [];
|
||||
|
||||
foreach (var modGroup in groups)
|
||||
{
|
||||
UpdateGroup(modGroup, ref num);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CustomMenuTwoCreate(GameOptionsMenu menu)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.ParentMenu == MenuCategory.CustomTwo) ?? [];
|
||||
foreach (var group in groups)
|
||||
{
|
||||
CreateGroup(menu, group);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ModifiersUpdate(ref float num)
|
||||
{
|
||||
var groups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
@@ -137,9 +188,19 @@ internal static class GameOptionsMenuPatch
|
||||
ModifiersCreate(__instance);
|
||||
return false;
|
||||
}
|
||||
else if (__instance.name == "CUSTOM TAB 1")
|
||||
{
|
||||
CustomMenuOneCreate(__instance);
|
||||
return false;
|
||||
}
|
||||
else if (__instance.name == "CUSTOM TAB 2")
|
||||
{
|
||||
CustomMenuTwoCreate(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
var filteredGroups = GameSettingMenuPatches.SelectedMod?.InternalOptionGroups
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu) ?? [];
|
||||
.Where(x => x.OptionableType == null && !x.ShowInModifiersMenu && x.ParentMenu == MenuCategory.Roles) ?? [];
|
||||
|
||||
foreach (var group in filteredGroups)
|
||||
{
|
||||
@@ -223,6 +284,9 @@ internal static class GameOptionsMenuPatch
|
||||
menu.playerOptionOrigin,
|
||||
menu.settingsContainer));
|
||||
|
||||
// TODO: Reimplement this without breaking the menu.
|
||||
// group.Options.ForEach(x => x.ChangeGameSetting());
|
||||
|
||||
foreach (var newOpt in options)
|
||||
{
|
||||
newOpt.SetClickMask(menu.ButtonClickMask);
|
||||
|
||||
@@ -117,7 +117,7 @@ internal static class GamePresetsTabPatches
|
||||
// ReSharper disable once InconsistentNaming
|
||||
public static void Postfix()
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("OnDisable called");
|
||||
Error("OnDisable called");
|
||||
|
||||
if (_saveButton)
|
||||
{
|
||||
@@ -155,7 +155,7 @@ internal static class GamePresetsTabPatches
|
||||
var prefab = GameSettingMenu.Instance.GameSettingsButton;
|
||||
if (prefab == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("GameSettingsButton prefab is null");
|
||||
Error("GameSettingsButton prefab is null");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -318,10 +318,10 @@ internal static class GamePresetsTabPatches
|
||||
|
||||
private static void Refresh()
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Refreshing presets");
|
||||
Error("Refreshing presets");
|
||||
if (GameSettingMenuPatches.SelectedMod == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Selected mod is null, cannot refresh presets");
|
||||
Error("Selected mod is null, cannot refresh presets");
|
||||
return;
|
||||
}
|
||||
lock (Lock)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Presets;
|
||||
@@ -27,13 +28,23 @@ internal static class GameSettingMenuPatches
|
||||
private static Vector3 _roleBtnOgPos;
|
||||
private static Vector3 _smallRoleBtnOgPos;
|
||||
private static Vector3 _modifierBtnOgPos;
|
||||
private static Vector3 _customOneBtnOgPos;
|
||||
private static Vector3 _customTwoBtnOgPos;
|
||||
|
||||
private static GameOptionsMenu? _modifiersTab;
|
||||
private static PassiveButton? _modifiersButton;
|
||||
private static PassiveButton? _smallRolesButton;
|
||||
private static GameOptionsMenu? _customOneTab;
|
||||
private static PassiveButton? _customOneButton;
|
||||
private static GameOptionsMenu? _customTwoTab;
|
||||
private static PassiveButton? _customTwoButton;
|
||||
private static GameObject? _nextModButton;
|
||||
private static GameObject? _previousModButton;
|
||||
|
||||
private static Dictionary<int, Vector3> OptionsPositions { get; } = [];
|
||||
private static Dictionary<int, Vector3> ModifiersPositions { get; } = [];
|
||||
private static Dictionary<int, Vector3> CustomOnePositions { get; } = [];
|
||||
private static Dictionary<int, Vector3> CustomTwoPositions { get; } = [];
|
||||
|
||||
private static void SaveScrollPositions(GameSettingMenu gameSettingMenu)
|
||||
{
|
||||
@@ -41,6 +52,14 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
ModifiersPositions[SelectedModIdx] = _modifiersTab!.scrollBar.Inner.localPosition;
|
||||
}
|
||||
else if (_customOneTab)
|
||||
{
|
||||
CustomOnePositions[SelectedModIdx] = _customOneTab!.scrollBar.Inner.localPosition;
|
||||
}
|
||||
else if (_customTwoTab)
|
||||
{
|
||||
CustomTwoPositions[SelectedModIdx] = _customTwoTab!.scrollBar.Inner.localPosition;
|
||||
}
|
||||
|
||||
RoleSettingMenuPatches.RolePositions[SelectedModIdx] = gameSettingMenu.RoleSettingsTab.scrollBar.Inner.localPosition;
|
||||
OptionsPositions[SelectedModIdx] = gameSettingMenu.GameSettingsTab.scrollBar.Inner.localPosition;
|
||||
@@ -65,9 +84,21 @@ internal static class GameSettingMenuPatches
|
||||
_modifiersTab!.gameObject.SetActive(tabNum == 3);
|
||||
}
|
||||
|
||||
if (_customOneTab)
|
||||
{
|
||||
_customOneTab!.gameObject.SetActive(tabNum == 4);
|
||||
}
|
||||
|
||||
if (_customTwoTab)
|
||||
{
|
||||
_customTwoTab!.gameObject.SetActive(tabNum == 5);
|
||||
}
|
||||
|
||||
_modifiersButton?.SelectButton(tabNum == 3);
|
||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||
if (tabNum == 3)
|
||||
_customOneButton?.SelectButton(tabNum == 4);
|
||||
_customTwoButton?.SelectButton(tabNum == 5);
|
||||
if (tabNum >= 3 && tabNum <= 5)
|
||||
{
|
||||
if (__instance.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
@@ -75,7 +106,18 @@ internal static class GameSettingMenuPatches
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiersButton?.SelectButton(true);
|
||||
switch (tabNum)
|
||||
{
|
||||
case 3:
|
||||
_modifiersButton?.SelectButton(true);
|
||||
break;
|
||||
case 4:
|
||||
_customOneButton?.SelectButton(true);
|
||||
break;
|
||||
case 5:
|
||||
_customTwoButton?.SelectButton(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,19 +129,30 @@ internal static class GameSettingMenuPatches
|
||||
|
||||
_modifiersButton?.SelectButton(tabNum == 3);
|
||||
_smallRolesButton?.SelectButton(tabNum == 2);
|
||||
_customOneButton?.SelectButton(tabNum == 4);
|
||||
_customTwoButton?.SelectButton(tabNum == 5);
|
||||
|
||||
if (tabNum != 3)
|
||||
if (tabNum >= 3 && tabNum <= 5)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (__instance.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
__instance.RoleSettingsButton.SelectButton(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiersButton?.SelectButton(true);
|
||||
if (__instance.RoleSettingsButton.gameObject.active)
|
||||
{
|
||||
__instance.RoleSettingsButton.SelectButton(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (tabNum)
|
||||
{
|
||||
case 3:
|
||||
_modifiersButton?.SelectButton(true);
|
||||
break;
|
||||
case 4:
|
||||
_customOneButton?.SelectButton(true);
|
||||
break;
|
||||
case 5:
|
||||
_customTwoButton?.SelectButton(true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,16 +180,16 @@ internal static class GameSettingMenuPatches
|
||||
|
||||
_text.alignment = TextAlignmentOptions.Center;
|
||||
|
||||
var nextButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
|
||||
nextButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
|
||||
nextButton.name = "RightArrowButton";
|
||||
nextButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||
_nextModButton = Object.Instantiate(__instance.BackButton, __instance.BackButton.transform.parent).gameObject;
|
||||
_nextModButton.transform.localPosition = new Vector3(-2.2663f, 1.5272f, -25f);
|
||||
_nextModButton.name = "RightArrowButton";
|
||||
_nextModButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||
MiraAssets.NextButton.LoadAsset();
|
||||
nextButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||
_nextModButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().sprite =
|
||||
MiraAssets.NextButtonActive.LoadAsset();
|
||||
nextButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
||||
_nextModButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().DestroyImmediate();
|
||||
|
||||
var passiveButton = nextButton.gameObject.GetComponent<PassiveButton>();
|
||||
var passiveButton = _nextModButton.gameObject.GetComponent<PassiveButton>();
|
||||
passiveButton.OnClick = new ButtonClickedEvent();
|
||||
passiveButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
@@ -151,13 +204,13 @@ internal static class GameSettingMenuPatches
|
||||
UpdateText(__instance, __instance.GameSettingsTab, __instance.RoleSettingsTab);
|
||||
}));
|
||||
|
||||
var backButton = Object.Instantiate(nextButton, __instance.BackButton.transform.parent).gameObject;
|
||||
backButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
|
||||
backButton.name = "LeftArrowButton";
|
||||
backButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
|
||||
backButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
|
||||
backButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
||||
backButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
|
||||
_previousModButton = Object.Instantiate(_nextModButton, __instance.BackButton.transform.parent).gameObject;
|
||||
_previousModButton.transform.localPosition = new Vector3(-4.4209f, 1.5272f, -25f);
|
||||
_previousModButton.name = "LeftArrowButton";
|
||||
_previousModButton.gameObject.GetComponent<CloseButtonConsoleBehaviour>().Destroy();
|
||||
_previousModButton.transform.FindChild("Active").gameObject.GetComponent<SpriteRenderer>().flipX =
|
||||
_previousModButton.transform.FindChild("Inactive").gameObject.GetComponent<SpriteRenderer>().flipX = true;
|
||||
_previousModButton.gameObject.GetComponent<PassiveButton>().OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
SaveScrollPositions(__instance);
|
||||
@@ -174,6 +227,61 @@ internal static class GameSettingMenuPatches
|
||||
_modifiersTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||
_modifiersTab.name = "MODIFIERS TAB";
|
||||
|
||||
// clone game settings tab for both custom tabs
|
||||
_customOneTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||
_customOneTab.name = "CUSTOM TAB 1";
|
||||
_customTwoTab = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
|
||||
_customTwoTab.name = "CUSTOM TAB 2";
|
||||
|
||||
// create button for the first custom category
|
||||
var customPos = new Vector3(
|
||||
__instance.RoleSettingsButton.transform.localPosition.x,
|
||||
__instance.RoleSettingsButton.transform.localPosition.y,
|
||||
__instance.RoleSettingsButton.transform.localPosition.z);
|
||||
_customOneButton = Object.Instantiate(__instance.RoleSettingsButton, __instance.RoleSettingsButton.transform.parent);
|
||||
_customOneButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
_customOneButton.OnClick = new ButtonClickedEvent();
|
||||
_customOneButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(4, false);
|
||||
}));
|
||||
_customOneButton.OnMouseOver = new UnityEvent();
|
||||
_customOneButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(4, true);
|
||||
}));
|
||||
|
||||
_customOneButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameOne : "Custom Category 1";
|
||||
customPos.y -= 0.637f;
|
||||
_customOneButton.transform.localPosition = customPos;
|
||||
_customOneButton.name = "CustomOneButton";
|
||||
|
||||
_customOneBtnOgPos = _customOneButton.transform.localPosition;
|
||||
|
||||
// create button for the first custom category
|
||||
_customTwoButton = Object.Instantiate(_customOneButton, _customOneButton.transform.parent);
|
||||
_customTwoButton.OnClick = new ButtonClickedEvent();
|
||||
_customTwoButton.OnClick.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(5, false);
|
||||
}));
|
||||
_customTwoButton.OnMouseOver = new UnityEvent();
|
||||
_customTwoButton.OnMouseOver.AddListener(
|
||||
(UnityAction)(() =>
|
||||
{
|
||||
__instance.ChangeTab(5, true);
|
||||
}));
|
||||
|
||||
_customTwoButton.buttonText.text = SelectedMod != null ? SelectedMod.MiraPlugin.CustomOptionMenuNameTwo : "Custom Category 2";
|
||||
customPos.y -= 0.637f;
|
||||
_customTwoButton.transform.localPosition = customPos;
|
||||
_customTwoButton.name = "CustomTwoButton";
|
||||
|
||||
_customTwoBtnOgPos = _customTwoButton.transform.localPosition;
|
||||
|
||||
// create button for modifiers
|
||||
__instance.RoleSettingsButton.buttonText.gameObject.GetComponent<TextTranslatorTMP>().Destroy();
|
||||
__instance.RoleSettingsButton.gameObject.SetActive(false);
|
||||
@@ -304,20 +412,32 @@ internal static class GameSettingMenuPatches
|
||||
bool replaceWithModifiers = true;
|
||||
_smallRolesButton!.transform.localPosition = _smallRoleBtnOgPos;
|
||||
_modifiersButton!.transform.localPosition = _modifierBtnOgPos;
|
||||
_customOneButton!.transform.localPosition = _customOneBtnOgPos;
|
||||
_customTwoButton!.transform.localPosition = _customTwoBtnOgPos;
|
||||
menu.RoleSettingsButton.transform.localPosition = _roleBtnOgPos;
|
||||
|
||||
if (SelectedModIdx != 0)
|
||||
{
|
||||
_customOneButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameOne;
|
||||
_customTwoButton.buttonText.text = SelectedMod!.MiraPlugin.CustomOptionMenuNameTwo;
|
||||
var modHasRoles = SelectedMod!.InternalRoles.Count != 0;
|
||||
var modHasCustomOne = SelectedMod.InternalOptionGroups.Exists(
|
||||
x => x.ParentMenu == MenuCategory.CustomOne);
|
||||
var modHasCustomTwo = SelectedMod.InternalOptionGroups.Exists(
|
||||
x => x.ParentMenu == MenuCategory.CustomTwo);
|
||||
var modHasModifiers = SelectedMod.InternalOptionGroups.Exists(
|
||||
x => x.ShowInModifiersMenu || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
||||
x => x.ShowInModifiersMenu || x.ParentMenu == MenuCategory.Modifiers || x.OptionableType?.IsAssignableTo(typeof(BaseModifier)) == true);
|
||||
var modHasOptions =
|
||||
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && !x.ShowInModifiersMenu);
|
||||
SelectedMod.InternalOptionGroups.Exists(x => x.OptionableType == null && (!x.ShowInModifiersMenu ||
|
||||
(x.ParentMenu != MenuCategory.Modifiers && x.ParentMenu != MenuCategory.Roles)));
|
||||
|
||||
_modifiersButton.gameObject.SetActive(true);
|
||||
_smallRolesButton.gameObject.SetActive(true);
|
||||
_customOneButton.gameObject.SetActive(true);
|
||||
_customTwoButton.gameObject.SetActive(true);
|
||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(false);
|
||||
var defaultButton = menu.RoleSettingsButton;
|
||||
|
||||
// If there are no registered custom roles in the selected mod, hide the button.
|
||||
if (!modHasRoles)
|
||||
@@ -334,6 +454,7 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
_modifiersButton.gameObject.SetActive(false);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
defaultButton = _modifiersButton;
|
||||
|
||||
if (_modifiersTab!.gameObject.active)
|
||||
{
|
||||
@@ -356,6 +477,7 @@ internal static class GameSettingMenuPatches
|
||||
// If the mod has roles, we can enable the bigger role button and disable the small one.
|
||||
if (modHasRoles)
|
||||
{
|
||||
defaultButton = menu.RoleSettingsButton;
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
_smallRolesButton.gameObject.SetActive(false);
|
||||
}
|
||||
@@ -370,6 +492,7 @@ internal static class GameSettingMenuPatches
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
defaultButton = menu.RoleSettingsButton;
|
||||
|
||||
// If the mod has roles and modifiers, we can move their buttons to the game settings button position, since nothing is there.
|
||||
if (menu.RoleSettingsButton.gameObject.active)
|
||||
@@ -389,16 +512,37 @@ internal static class GameSettingMenuPatches
|
||||
_smallRolesButton.transform.localPosition.z);
|
||||
}
|
||||
}
|
||||
|
||||
// If the mod has no primary custom tab, we can go ahead and hide the button.
|
||||
if (!modHasCustomOne)
|
||||
{
|
||||
_customOneButton.gameObject.SetActive(false);
|
||||
if (_customOneTab!.gameObject.active)
|
||||
{
|
||||
defaultButton.SelectButton(true);
|
||||
}
|
||||
}
|
||||
// If the mod has no secondary custom tab, we can go ahead and hide the button.
|
||||
if (!modHasCustomTwo)
|
||||
{
|
||||
_customTwoButton.gameObject.SetActive(false);
|
||||
if (_customTwoTab!.gameObject.active)
|
||||
{
|
||||
defaultButton.SelectButton(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_modifiersButton.gameObject.SetActive(false);
|
||||
_smallRolesButton.gameObject.SetActive(false);
|
||||
_customOneButton.gameObject.SetActive(false);
|
||||
_customTwoButton.gameObject.SetActive(false);
|
||||
menu.RoleSettingsButton.gameObject.SetActive(true);
|
||||
menu.GameSettingsButton.gameObject.SetActive(true);
|
||||
replaceWithModifiers = false;
|
||||
|
||||
if (_modifiersTab!.gameObject.active)
|
||||
if (_modifiersTab!.gameObject.active || _customOneTab!.gameObject.active || _customTwoTab!.gameObject.active)
|
||||
{
|
||||
menu.ChangeTab(0, false);
|
||||
}
|
||||
@@ -446,7 +590,17 @@ internal static class GameSettingMenuPatches
|
||||
|
||||
if (_modifiersTab?.Children?.Count > 0)
|
||||
{
|
||||
CleanupSettings(_modifiersTab, true);
|
||||
CleanupSettings(_modifiersTab, MenuCategory.Modifiers);
|
||||
}
|
||||
|
||||
if (_customOneTab?.Children?.Count > 0)
|
||||
{
|
||||
CleanupSettings(_customOneTab, MenuCategory.CustomOne);
|
||||
}
|
||||
|
||||
if (_customTwoTab?.Children?.Count > 0)
|
||||
{
|
||||
CleanupSettings(_customTwoTab, MenuCategory.CustomTwo);
|
||||
}
|
||||
|
||||
void CleanupRoleSettings(RolesSettingsMenu rolesMenu)
|
||||
@@ -484,7 +638,7 @@ internal static class GameSettingMenuPatches
|
||||
}
|
||||
}
|
||||
|
||||
void CleanupSettings(GameOptionsMenu gameOptMenu, bool modifiers = false)
|
||||
void CleanupSettings(GameOptionsMenu gameOptMenu, MenuCategory menuCategory = MenuCategory.Roles)
|
||||
{
|
||||
ClearOptions(gameOptMenu.Children);
|
||||
gameOptMenu.Children = null;
|
||||
@@ -495,7 +649,19 @@ internal static class GameSettingMenuPatches
|
||||
.ForEach(header => header.gameObject.DestroyImmediate());
|
||||
|
||||
gameOptMenu.Initialize();
|
||||
var positions = modifiers ? ModifiersPositions : OptionsPositions;
|
||||
var positions = OptionsPositions;
|
||||
switch (menuCategory)
|
||||
{
|
||||
case MenuCategory.Modifiers:
|
||||
positions = ModifiersPositions;
|
||||
break;
|
||||
case MenuCategory.CustomOne:
|
||||
positions = CustomOnePositions;
|
||||
break;
|
||||
case MenuCategory.CustomTwo:
|
||||
positions = CustomTwoPositions;
|
||||
break;
|
||||
}
|
||||
if (positions.TryGetValue(SelectedModIdx, out var pos))
|
||||
{
|
||||
gameOptMenu.scrollBar.Inner.localPosition = pos;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.GameOptions;
|
||||
using MiraAPI.Modifiers;
|
||||
@@ -163,7 +164,108 @@ public static class LobbyViewPanePatches
|
||||
{
|
||||
if (SelectedModIdx == 0)
|
||||
{
|
||||
return true;
|
||||
float num = 0.95f;
|
||||
float num2 = -6.53f;
|
||||
CategoryHeaderMasked categoryHeaderMasked =
|
||||
Object.Instantiate(__instance.categoryHeaderOrigin, __instance.settingsContainer);
|
||||
categoryHeaderMasked.SetHeader(StringNames.RoleQuotaLabel, 61);
|
||||
categoryHeaderMasked.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, 1.26f, -2f);
|
||||
__instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
|
||||
var list = new List<RoleBehaviour>();
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
var categoryHeaderRoleVariant =
|
||||
Object.Instantiate(__instance.categoryHeaderRoleOrigin, __instance.settingsContainer);
|
||||
categoryHeaderRoleVariant.SetHeader(
|
||||
(i == 0) ? StringNames.CrewmateRolesHeader : StringNames.ImpostorRolesHeader,
|
||||
61);
|
||||
categoryHeaderRoleVariant.transform.localScale = Vector3.one;
|
||||
categoryHeaderRoleVariant.transform.localPosition = new Vector3(0.09f, num, -2f);
|
||||
__instance.settingsInfo.Add(categoryHeaderRoleVariant.gameObject);
|
||||
num -= 0.696f;
|
||||
var roles = RoleManager.Instance.AllRoles.ToArray().Where(x =>
|
||||
x is not ICustomRole && x.Role != RoleTypes.Crewmate && x.Role != RoleTypes.Impostor &&
|
||||
x.Role != RoleTypes.CrewmateGhost && x.Role != RoleTypes.ImpostorGhost).ToList();
|
||||
for (int j = 0; j < roles.Count; j++)
|
||||
{
|
||||
var roleBehaviour = roles[j];
|
||||
int chancePerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions.GetChancePerGame(
|
||||
roleBehaviour.Role);
|
||||
int numPerGame =
|
||||
GameOptionsManager.Instance.CurrentGameOptions.RoleOptions
|
||||
.GetNumPerGame(roleBehaviour.Role);
|
||||
bool flag = numPerGame == 0;
|
||||
var viewSettingsInfoPanelRoleVariant =
|
||||
Object.Instantiate(
|
||||
__instance.infoPanelRoleOrigin,
|
||||
__instance.settingsContainer);
|
||||
viewSettingsInfoPanelRoleVariant.transform.localScale = Vector3.one;
|
||||
viewSettingsInfoPanelRoleVariant.transform.localPosition = new Vector3(num2, num, -2f);
|
||||
if (!flag)
|
||||
{
|
||||
list.Add(roleBehaviour);
|
||||
}
|
||||
|
||||
viewSettingsInfoPanelRoleVariant.SetInfo(
|
||||
roleBehaviour.NiceName,
|
||||
numPerGame,
|
||||
chancePerGame,
|
||||
61,
|
||||
(i == 0) ? Palette.CrewmateRoleBlue : Palette.ImpostorRoleRed,
|
||||
roleBehaviour.RoleIconSolid,
|
||||
i == 0,
|
||||
flag);
|
||||
__instance.settingsInfo.Add(viewSettingsInfoPanelRoleVariant.gameObject);
|
||||
num -= 0.664f;
|
||||
}
|
||||
}
|
||||
|
||||
if (list.Count > 0)
|
||||
{
|
||||
var categoryHeaderMasked2 =
|
||||
Object.Instantiate(__instance.categoryHeaderOrigin, __instance.settingsContainer);
|
||||
categoryHeaderMasked2.SetHeader(StringNames.RoleSettingsLabel, 61);
|
||||
categoryHeaderMasked2.transform.localScale = Vector3.one;
|
||||
categoryHeaderMasked2.transform.localPosition = new Vector3(-9.77f, num, -2f);
|
||||
__instance.settingsInfo.Add(categoryHeaderMasked2.gameObject);
|
||||
num -= 2.1f;
|
||||
float num3 = 0f;
|
||||
for (int k = 0; k < list.Count; k++)
|
||||
{
|
||||
float num4;
|
||||
if (k % 2 == 0)
|
||||
{
|
||||
num4 = -5.8f;
|
||||
if (k > 0)
|
||||
{
|
||||
num -= num3 + 0.85f;
|
||||
num3 = 0f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
num4 = 0.14999962f;
|
||||
}
|
||||
|
||||
var advancedRoleViewPanel =
|
||||
Object.Instantiate(__instance.advancedRolePanelOrigin, __instance.settingsContainer);
|
||||
advancedRoleViewPanel.transform.localScale = Vector3.one;
|
||||
advancedRoleViewPanel.transform.localPosition = new Vector3(num4, num, -2f);
|
||||
float num5 = advancedRoleViewPanel.SetUp(list[k], 0.85f, 61);
|
||||
if (num5 > num3)
|
||||
{
|
||||
num3 = num5;
|
||||
}
|
||||
|
||||
__instance.settingsInfo.Add(advancedRoleViewPanel.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
__instance.scrollBar.SetYBoundsMax(-num);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (__instance.currentTab == ModifiersTabName)
|
||||
@@ -344,7 +446,7 @@ public static class LobbyViewPanePatches
|
||||
}
|
||||
|
||||
viewSettingsInfoPanelRoleVariant.SetInfo(
|
||||
roleBehaviour.NiceName,
|
||||
roleBehaviour.GetRoleName(),
|
||||
numPerGame,
|
||||
chancePerGame,
|
||||
61,
|
||||
|
||||
@@ -216,7 +216,16 @@ public static class OptionsPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.Value += __instance.Increment;
|
||||
var increment = __instance.Increment;
|
||||
|
||||
if (Input.GetKey(KeyCode.LeftShift) &&
|
||||
__instance.TryGetComponent<MiraNumberOptionComponent>(out var miraNumber) &&
|
||||
miraNumber.ShiftIncrementToggle)
|
||||
{
|
||||
increment /= 2;
|
||||
}
|
||||
|
||||
__instance.Value += increment;
|
||||
if (__instance.Value > __instance.ValidRange.max)
|
||||
{
|
||||
__instance.Value = __instance.ValidRange.min;
|
||||
@@ -235,7 +244,16 @@ public static class OptionsPatches
|
||||
return true;
|
||||
}
|
||||
|
||||
__instance.Value -= __instance.Increment;
|
||||
var increment = __instance.Increment;
|
||||
|
||||
if (Input.GetKey(KeyCode.LeftShift) &&
|
||||
__instance.TryGetComponent<MiraNumberOptionComponent>(out var miraNumber) &&
|
||||
miraNumber.ShiftIncrementToggle)
|
||||
{
|
||||
increment /= 2;
|
||||
}
|
||||
|
||||
__instance.Value -= increment;
|
||||
if (__instance.Value < __instance.ValidRange.min)
|
||||
{
|
||||
__instance.Value = __instance.ValidRange.max;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.GameOptions;
|
||||
@@ -57,6 +58,7 @@ public static class RoleSettingMenuPatches
|
||||
|
||||
var maskBg = __instance.scrollBar.transform.FindChild("MaskBg");
|
||||
var hitbox = __instance.scrollBar.transform.FindChild("Hitbox");
|
||||
var dividerImage = __instance.transform.FindChild("HeaderButtons/DividerImage");
|
||||
|
||||
if (GameSettingMenuPatches.SelectedModIdx == 0)
|
||||
{
|
||||
@@ -67,7 +69,64 @@ public static class RoleSettingMenuPatches
|
||||
maskBg.localScale = new Vector3(6.6811f, 3.3563f, 0.5598f);
|
||||
hitbox.localPosition = new Vector3(0.3297f, -.2333f, 4f);
|
||||
hitbox.localScale = new Vector3(1, 1, 1);
|
||||
return true;
|
||||
dividerImage.gameObject.SetActive(true);
|
||||
|
||||
var num = 0.662f;
|
||||
|
||||
var list = CustomRoleManager.AllRoles.Where(
|
||||
r => !r.IsCustomRole() && r.TeamType == RoleTeamTypes.Crewmate && r.Role != RoleTypes.Crewmate &&
|
||||
r.Role != RoleTypes.CrewmateGhost).ToList();
|
||||
var list2 = CustomRoleManager.AllRoles.Where(
|
||||
r => !r.IsCustomRole() && r.TeamType == RoleTeamTypes.Impostor && r.Role != RoleTypes.Impostor &&
|
||||
r.Role != RoleTypes.ImpostorGhost).ToList();
|
||||
|
||||
if (__instance.roleTabs == null || __instance.roleTabs.Count == 0)
|
||||
{
|
||||
var num2 = -1.928f;
|
||||
__instance.roleTabs = new();
|
||||
__instance.roleTabs.Add(__instance.AllButton);
|
||||
foreach (var t in list)
|
||||
{
|
||||
__instance.AddRoleTab(t, ref num2);
|
||||
}
|
||||
|
||||
foreach (var t in list2)
|
||||
{
|
||||
__instance.AddRoleTab(t, ref num2);
|
||||
}
|
||||
}
|
||||
|
||||
var categoryHeaderEditRole = Object.Instantiate(
|
||||
__instance.categoryHeaderEditRoleOrigin,
|
||||
Vector3.zero,
|
||||
Quaternion.identity,
|
||||
__instance.RoleChancesSettings.transform);
|
||||
categoryHeaderEditRole.SetHeader(StringNames.CrewmateRolesHeader, 20);
|
||||
categoryHeaderEditRole.transform.localPosition = new Vector3(4.986f, num, -2f);
|
||||
num -= 0.522f;
|
||||
var num3 = 0;
|
||||
foreach (var t in list)
|
||||
{
|
||||
__instance.CreateQuotaOption(t, ref num, num3);
|
||||
num3++;
|
||||
}
|
||||
|
||||
num -= 0.22f;
|
||||
var categoryHeaderEditRole2 = Object.Instantiate(
|
||||
__instance.categoryHeaderEditRoleOrigin,
|
||||
Vector3.zero,
|
||||
Quaternion.identity,
|
||||
__instance.RoleChancesSettings.transform);
|
||||
categoryHeaderEditRole2.SetHeader(StringNames.ImpostorRolesHeader, 20);
|
||||
categoryHeaderEditRole2.transform.localPosition = new Vector3(4.986f, num, -2f);
|
||||
num -= 0.522f;
|
||||
foreach (var t in list2)
|
||||
{
|
||||
__instance.CreateQuotaOption(t, ref num, num3);
|
||||
num3++;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ScrollerNum = 0.522f;
|
||||
@@ -79,15 +138,16 @@ public static class RoleSettingMenuPatches
|
||||
maskBg.localScale = new Vector3(6.6811f, 4.1563f, 0.5598f);
|
||||
hitbox.localPosition = new Vector3(0.3297f, -.6333f, 4f);
|
||||
hitbox.localScale = new Vector3(1, 1.2f, 1);
|
||||
dividerImage.gameObject.SetActive(false);
|
||||
|
||||
var num3 = 0;
|
||||
var num4 = 0;
|
||||
|
||||
var roleGroups = GameSettingMenuPatches.SelectedMod?.InternalRoles.Values.OfType<ICustomRole>()
|
||||
.ToLookup(x => x.RoleOptionsGroup);
|
||||
|
||||
if (roleGroups is null)
|
||||
{
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
// sort the groups by priority
|
||||
@@ -197,11 +257,11 @@ public static class RoleSettingMenuPatches
|
||||
continue;
|
||||
}
|
||||
|
||||
var option = CreateQuotaOption(__instance, roleBehaviour, num3);
|
||||
var option = CreateQuotaOption(__instance, roleBehaviour, num4);
|
||||
if (option is not null)
|
||||
{
|
||||
RoleOptionSettings.Add(option);
|
||||
num3++;
|
||||
num4++;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +392,7 @@ public static class RoleSettingMenuPatches
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Warning(e);
|
||||
Warning(e);
|
||||
}
|
||||
|
||||
roleSetting.UpdateValuesAndText(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions);
|
||||
@@ -406,16 +466,14 @@ public static class RoleSettingMenuPatches
|
||||
{
|
||||
if (role is not ICustomRole customRole)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Role {role.NiceName} is not a custom role.");
|
||||
Error($"Role {role.NiceName} is not a custom role.");
|
||||
return;
|
||||
}
|
||||
|
||||
RolePositions[GameSettingMenuPatches.SelectedModIdx] = __instance.scrollBar.Inner.localPosition;
|
||||
|
||||
__instance.roleDescriptionText.text = customRole.RoleLongDescription;
|
||||
__instance.roleTitleText.text = TranslationController.Instance.GetString(
|
||||
role.StringName,
|
||||
new Il2CppReferenceArray<Il2CppSystem.Object>(0));
|
||||
__instance.roleTitleText.text = role.GetRoleName();
|
||||
|
||||
var imgBg = __instance.AdvancedRolesSettings.transform.FindChild("Imagebackground");
|
||||
var labelBg = __instance.AdvancedRolesSettings.transform.FindChild("InfoLabelBackground");
|
||||
@@ -502,7 +560,7 @@ public static class RoleSettingMenuPatches
|
||||
{
|
||||
if (role is not ICustomRole customRole)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Role {role.NiceName} is not a custom role.");
|
||||
Error($"Role {role.NiceName} is not a custom role.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -519,6 +577,7 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.transform.localPosition = new Vector3(-0.1f, ScrollerNum, -2f);
|
||||
|
||||
roleOptionSetting.SetRole(GameOptionsManager.Instance.CurrentGameOptions.RoleOptions, role, 20);
|
||||
roleOptionSetting.titleText.text = role.GetRoleName();
|
||||
roleOptionSetting.labelSprite.color = customRole.OptionsMenuColor;
|
||||
roleOptionSetting.OnValueChanged = new Action<OptionBehaviour>(ValueChanged);
|
||||
roleOptionSetting.SetClickMask(__instance.ButtonClickMask);
|
||||
@@ -532,6 +591,19 @@ public static class RoleSettingMenuPatches
|
||||
roleOptionSetting.titleText.color = customRole.OptionsMenuColor.FindAlternateColor();
|
||||
roleOptionSetting.titleText.horizontalAlignment = HorizontalAlignmentOptions.Left;
|
||||
|
||||
if (customRole.Configuration.Icon != null)
|
||||
{
|
||||
var roleIcon = new GameObject("RoleIcon");
|
||||
roleIcon.transform.parent = roleOptionSetting.transform;
|
||||
roleIcon.transform.localScale = new(.25f, .25f, 1);
|
||||
roleIcon.layer = LayerMask.NameToLayer("UI");
|
||||
roleIcon.transform.localPosition = new Vector3(-1.3f, -0.3f, -2f);
|
||||
var rend = roleIcon.AddComponent<SpriteRenderer>();
|
||||
rend.sprite = customRole.Configuration.Icon.LoadAsset();
|
||||
|
||||
rend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||
}
|
||||
|
||||
if (GameSettingMenuPatches.SelectedMod is null ||
|
||||
GameSettingMenuPatches.SelectedMod.InternalOptionGroups
|
||||
.Exists(x => x.GroupVisible() && x.OptionableType == role.GetType() && x.Options.Any(y => y.Visible())))
|
||||
@@ -551,18 +623,6 @@ public static class RoleSettingMenuPatches
|
||||
passiveButton.interactableHoveredColor = Color.white;
|
||||
|
||||
passiveButton.OnClick.AddListener((UnityAction)(() => { ChangeTab(role, __instance); }));
|
||||
|
||||
if (customRole.Configuration.Icon != null)
|
||||
{
|
||||
var roleIcon = new GameObject("RoleIcon");
|
||||
roleIcon.transform.parent = roleOptionSetting.transform;
|
||||
roleIcon.transform.localScale = new(.25f, .25f, 1);
|
||||
roleIcon.layer = LayerMask.NameToLayer("UI");
|
||||
roleIcon.transform.localPosition = new Vector3(-1.3f, -0.3f, -2f);
|
||||
var rend = roleIcon.AddComponent<SpriteRenderer>();
|
||||
rend.sprite = customRole.Configuration.Icon.LoadAsset();
|
||||
rend.maskInteraction = SpriteMaskInteraction.VisibleInsideMask;
|
||||
}
|
||||
}
|
||||
|
||||
if (customRole.Configuration is { MaxRoleCount: 0 })
|
||||
|
||||
@@ -39,6 +39,7 @@ public static class SettingPatches
|
||||
MiraNumberSuffixes.None => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo),
|
||||
MiraNumberSuffixes.Multiplier => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "x",
|
||||
MiraNumberSuffixes.Percent => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + "%",
|
||||
MiraNumberSuffixes.Distance => value.ToString(__instance.FormatString, NumberFormatInfo.InvariantInfo) + MiraApiPlugin.DistanceSuffix.Value,
|
||||
_ => TranslationController.Instance.GetString(
|
||||
StringNames.GameSecondsAbbrev,
|
||||
(Il2CppSystem.Object[])[value.ToString(__instance.FormatString, CultureInfo.InvariantCulture)]),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Linq;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Events;
|
||||
using MiraAPI.Events.Vanilla.Gameplay;
|
||||
@@ -67,15 +68,53 @@ internal static class PlayerControlPatches
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(PlayerControl.RpcMurderPlayer))]
|
||||
// ReSharper disable once InconsistentNaming
|
||||
// Note: This method is partially inlined in 2025.5.20 but we already use custom murder RPCs.
|
||||
public static void PlayerControlMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool didSucceed)
|
||||
[HarmonyPatch(nameof(PlayerControl.CheckMurder))]
|
||||
public static void PlayerControlCheckMurderPrefix(PlayerControl __instance, PlayerControl target, ref bool __runOriginal)
|
||||
{
|
||||
__runOriginal = false;
|
||||
|
||||
__instance.logger.Debug($"Checking if {__instance.PlayerId} murdered {(target == null ? "null player" : target.PlayerId.ToString(NumberFormatInfo.InvariantInfo))}");
|
||||
|
||||
__instance.isKilling = false;
|
||||
if (AmongUsClient.Instance.IsGameOver || !AmongUsClient.Instance.AmHost)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!target || __instance.Data.IsDead || !__instance.Data.Role.IsImpostor || __instance.Data.Disconnected)
|
||||
{
|
||||
int num = target ? target!.PlayerId : -1;
|
||||
__instance.logger.Warning($"Bad kill from {__instance.PlayerId} to {num}");
|
||||
__instance.RpcMurderPlayer(target, false);
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkedPlayerInfo data = target!.Data;
|
||||
if (data == null || data.IsDead || target.inVent || target.MyPhysics.Animations.IsPlayingEnterVentAnimation() ||
|
||||
target.MyPhysics.Animations.IsPlayingAnyLadderAnimation() || target.inMovingPlat)
|
||||
{
|
||||
__instance.logger.Warning("Invalid target data for kill");
|
||||
__instance.RpcMurderPlayer(target, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (MeetingHud.Instance)
|
||||
{
|
||||
__instance.logger.Warning("Tried to kill while a meeting was starting");
|
||||
__instance.RpcMurderPlayer(target, false);
|
||||
return;
|
||||
}
|
||||
|
||||
var beforeMurderEvent = new BeforeMurderEvent(__instance, target);
|
||||
MiraEventManager.InvokeEvent(beforeMurderEvent);
|
||||
|
||||
didSucceed = beforeMurderEvent.IsCancelled;
|
||||
if (beforeMurderEvent.IsCancelled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
__instance.isKilling = true;
|
||||
__instance.RpcMurderPlayer(target, true);
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
@@ -88,17 +127,19 @@ internal static class PlayerControlPatches
|
||||
return;
|
||||
}
|
||||
|
||||
var role = __instance.Data?.Role;
|
||||
foreach (var button in CustomButtonManager.CustomButtons)
|
||||
{
|
||||
if (__instance.Data?.Role == null)
|
||||
if (role == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!button.Enabled(__instance.Data?.Role))
|
||||
if (!button.Enabled(role))
|
||||
{
|
||||
button.SetActive(false, role);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -106,7 +147,7 @@ internal static class PlayerControlPatches
|
||||
}
|
||||
catch (System.Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to update custom button {button.GetType().Name}: {e}");
|
||||
Error($"Failed to update custom button {button.GetType().Name}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,8 @@ internal static class EjectionPatches
|
||||
__instance.completeString = role.GetCustomEjectionMessage(__instance.initData.networkedPlayer);
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.WrapUp))]
|
||||
public static class WrapUpPatch
|
||||
[HarmonyPatch(typeof(ExileController), nameof(ExileController.ReEnableGameplay))]
|
||||
public static class ReEnableGameplayPatch
|
||||
{
|
||||
public static void Postfix()
|
||||
{
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
/// <summary>
|
||||
/// Patches the haunt menu to show the actual role name rather then team.
|
||||
/// Patches the haunt menu to show the actual role name rather than team.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(HauntMenuMinigame), nameof(HauntMenuMinigame.SetHauntTarget))]
|
||||
// SetFilterText was INLINED
|
||||
@@ -22,7 +23,7 @@ public static class HauntMenuMinigamePatch
|
||||
var role = __instance.HauntTarget.Data.Role;
|
||||
var color = role is ICustomRole custom ? custom.RoleColor : role.TeamColor;
|
||||
|
||||
__instance.FilterText.text = role.NiceName;
|
||||
__instance.FilterText.text = role.GetRoleName();
|
||||
__instance.FilterText.color = color;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using HarmonyLib;
|
||||
using MiraAPI.Roles;
|
||||
using UnityEngine;
|
||||
|
||||
namespace MiraAPI.Patches.Roles;
|
||||
|
||||
[HarmonyPatch(typeof(HowToPlayScene), nameof(HowToPlayScene.OpenRolesSelectionMenu))]
|
||||
internal static class HowToPlayPatch
|
||||
{
|
||||
// yes i patched the entire method
|
||||
private static void Prefix(HowToPlayScene __instance)
|
||||
{
|
||||
__instance.sceneIndex = 0;
|
||||
__instance.category = HowToPlayScene.HowToPlayCategory.RolesSelection;
|
||||
__instance.startPage.SetActive(false);
|
||||
if (__instance.roleButtonsParent.childCount == 0)
|
||||
{
|
||||
using (IEnumerator<RoleBehaviour> enumerator = RoleManager.Instance.AllRoles.ToArray().Where(x => !x.IsCustomRole()).GetEnumerator())
|
||||
{
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
RoleBehaviour role = enumerator.Current;
|
||||
if (!role.IsSimpleRole && role.Role != RoleTypes.CrewmateGhost && role.Role != RoleTypes.ImpostorGhost)
|
||||
{
|
||||
HowToPlayRoleButton component = Object.Instantiate(__instance.roleButtonPrefab, __instance.roleButtonsParent).GetComponent<HowToPlayRoleButton>();
|
||||
Sprite roleIcon = __instance.rolesScenes.ToArray().First(r => r.role == role.Role).roleIcon;
|
||||
component.SetRoleInfo(role, roleIcon);
|
||||
component.SetButtonAction((Il2CppSystem.Action)(() =>
|
||||
{
|
||||
__instance.OpenRolePage(role.Role);
|
||||
}));
|
||||
__instance.controllerSelectables.Add(component.GetComponent<PassiveButton>());
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (UiElement uiElement in __instance.controllerSelectables)
|
||||
{
|
||||
uiElement.ReceiveMouseOut();
|
||||
}
|
||||
ControllerManager.Instance.NewScene(__instance.name, __instance.closeButton, __instance.defaultButtonSelected, __instance.controllerSelectables, false);
|
||||
}
|
||||
__instance.DisableAllScenes();
|
||||
__instance.roleSelectionScene.SetActive(true);
|
||||
ControllerManager.Instance.SetDefaultSelection(__instance.defaultButtonSelected, null);
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,11 @@ public static class KillButtonPatches
|
||||
return false;
|
||||
}
|
||||
|
||||
if (PlayerControl.LocalPlayer.Data.Role is not ICustomRole)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
PlayerControl.LocalPlayer.RpcCustomMurder(__instance.currentTarget);
|
||||
__instance.SetTarget(null);
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ public static class RoleManagerPatches
|
||||
var data = targetPlayer.Data;
|
||||
if (data == null)
|
||||
{
|
||||
Debug.LogError("It shouldn't be possible, but " + targetPlayer.name + " still doesn't have PlayerData during role selection.");
|
||||
Error("It shouldn't be possible, but " + targetPlayer.name + " still doesn't have PlayerData during role selection.");
|
||||
return false;
|
||||
}
|
||||
if (data.Role)
|
||||
|
||||
@@ -9,17 +9,17 @@ namespace MiraAPI.Patches.Roles;
|
||||
/// <summary>
|
||||
/// Patches to return the correct role counts.
|
||||
/// </summary>
|
||||
[HarmonyPatch(typeof(RoleOptionsCollectionV09))]
|
||||
[HarmonyPatch(typeof(RoleOptionsCollectionV10))]
|
||||
public static class RoleOptionsCollectionPatch
|
||||
{
|
||||
/// <summary>
|
||||
/// This patch fixes GetNumPerGame being inlined (2025.5.20) in the original code.
|
||||
/// This patch fixes GetNumPerGame being inlined (2025.9.9) in the original code.
|
||||
/// </summary>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV09.AnyRolesEnabled))]
|
||||
public static bool AnyRolesEnabledPrefix(RoleOptionsCollectionV09 __instance, ref bool __result)
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV10.AnyRolesEnabled))]
|
||||
public static bool AnyRolesEnabledPrefix(RoleOptionsCollectionV10 __instance, ref bool __result)
|
||||
{
|
||||
foreach (KeyValuePair<RoleTypes, RoleDataV09> keyValuePair in __instance.roles)
|
||||
foreach (KeyValuePair<RoleTypes, RoleDataV10> keyValuePair in __instance.roles)
|
||||
{
|
||||
if (__instance.GetNumPerGame(keyValuePair.Key) > 0)
|
||||
{
|
||||
@@ -34,7 +34,7 @@ public static class RoleOptionsCollectionPatch
|
||||
/// </summary>
|
||||
/// <returns>Return false to skip original method, true to not.</returns>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetChancePerGame))]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV10.GetChancePerGame))]
|
||||
public static bool GetChancePrefix(RoleTypes role, ref int __result)
|
||||
{
|
||||
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
|
||||
@@ -51,7 +51,7 @@ public static class RoleOptionsCollectionPatch
|
||||
var chance = customRole.GetChance();
|
||||
if (chance == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Chance is null, defaulting to zero.");
|
||||
Error($"Chance is null, defaulting to zero.");
|
||||
chance = 0;
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public static class RoleOptionsCollectionPatch
|
||||
/// </summary>
|
||||
/// <returns>Return false to skip original method, true to not.</returns>
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV09.GetNumPerGame))]
|
||||
[HarmonyPatch(nameof(RoleOptionsCollectionV10.GetNumPerGame))]
|
||||
public static bool GetNumPrefix(RoleTypes role, ref int __result)
|
||||
{
|
||||
if (!CustomRoleManager.GetCustomRoleBehaviour(role, out var customRole) || customRole == null)
|
||||
@@ -81,7 +81,7 @@ public static class RoleOptionsCollectionPatch
|
||||
var count = customRole.GetCount();
|
||||
if (count == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Count is null, defaulting to zero.");
|
||||
Error($"Count is null, defaulting to zero.");
|
||||
count = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using HarmonyLib;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Reactor.Utilities;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
@@ -8,20 +10,29 @@ namespace MiraAPI.Patches;
|
||||
[HarmonyPatch(typeof(ServerDropdown), nameof(ServerDropdown.FillServerOptions))]
|
||||
public static class ServerDropdownPatch
|
||||
{
|
||||
public static bool RegionEquals(this IRegionInfo region, IRegionInfo other)
|
||||
{
|
||||
return region.Name == other.Name &&
|
||||
region.TranslateName == other.TranslateName &&
|
||||
region.PingServer == other.PingServer &&
|
||||
region.TargetServer == other.TargetServer &&
|
||||
region.Servers.All(s=>other.Servers.Any(x=>x.Equals(s)));
|
||||
}
|
||||
|
||||
public static bool Prefix(ServerDropdown __instance)
|
||||
{
|
||||
var num = 0;
|
||||
__instance.background.size = new Vector2(8.4f, 4.8f);
|
||||
|
||||
foreach (var regionInfo in DestroyableSingleton<ServerManager>.Instance.AvailableRegions)
|
||||
foreach (var regionInfo in ServerManager.Instance.AvailableRegions)
|
||||
{
|
||||
var findingGame = SceneManager.GetActiveScene().name is "FindAGame";
|
||||
|
||||
if (DestroyableSingleton<ServerManager>.Instance.CurrentRegion.Equals(regionInfo))
|
||||
if (ServerManager.Instance.CurrentRegion.RegionEquals(regionInfo))
|
||||
{
|
||||
__instance.defaultButtonSelected = __instance.firstOption;
|
||||
__instance.firstOption.ChangeButtonText(
|
||||
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
|
||||
TranslationController.Instance.GetStringWithDefault(
|
||||
regionInfo.TranslateName,
|
||||
regionInfo.Name));
|
||||
}
|
||||
@@ -34,11 +45,11 @@ public static class ServerDropdownPatch
|
||||
{
|
||||
x += 2;
|
||||
}
|
||||
var y = -0.55f * (num / 2);
|
||||
var y = -0.55f * (num / 2f);
|
||||
serverListButton.transform.localPosition = new Vector3(x, __instance.y_posButton + y, -1f);
|
||||
serverListButton.transform.localScale = Vector3.one;
|
||||
serverListButton.Text.text =
|
||||
DestroyableSingleton<TranslationController>.Instance.GetStringWithDefault(
|
||||
TranslationController.Instance.GetStringWithDefault(
|
||||
regionInfo.TranslateName,
|
||||
regionInfo.Name);
|
||||
serverListButton.Text.ForceMeshUpdate();
|
||||
@@ -47,9 +58,9 @@ public static class ServerDropdownPatch
|
||||
__instance.controllerSelectable.Add(serverListButton.Button);
|
||||
__instance.background.transform.localPosition = new Vector3(
|
||||
findingGame ? 2f : 0f,
|
||||
__instance.initialYPos + (-0.3f * (num / 2)),
|
||||
__instance.initialYPos + -0.3f * (num / 2f),
|
||||
0f);
|
||||
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + (0.6f * (num / 2)));
|
||||
__instance.background.size = new Vector2(__instance.background.size.x, 1.2f + 0.6f * (num / 2f));
|
||||
num++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
using HarmonyLib;
|
||||
using Hazel;
|
||||
using Il2CppSystem.Text;
|
||||
|
||||
namespace MiraAPI.Patches.Stubs;
|
||||
@@ -229,4 +230,76 @@ public static class RoleBehaviourStubs
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub method for RoleBehaviour.UseSecondaryAbility.
|
||||
/// </summary>
|
||||
/// <param name="instance">The RoleBehaviour object.</param>
|
||||
[HarmonyReversePatch]
|
||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.UseSecondaryAbility))]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void UseSecondaryAbility(RoleBehaviour instance)
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub method for RoleBehaviour.OnRoleSet.
|
||||
/// </summary>
|
||||
/// <param name="instance">The RoleBehaviour object.</param>
|
||||
[HarmonyReversePatch]
|
||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.OnRoleSet))]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void OnRoleSet(RoleBehaviour instance)
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub method for RoleBehaviour.InitializeMeetingAbilityButton.
|
||||
/// </summary>
|
||||
/// <param name="instance">The RoleBehaviour object.</param>
|
||||
[HarmonyReversePatch]
|
||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.InitializeMeetingAbilityButton))]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void InitializeMeetingAbilityButton(RoleBehaviour instance)
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub method for RoleBehaviour.FindClosestBody.
|
||||
/// </summary>
|
||||
/// <param name="instance">The RoleBehaviour object.</param>
|
||||
[HarmonyReversePatch]
|
||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.FindClosestBody))]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static DeadBody FindClosestBody(RoleBehaviour instance)
|
||||
{
|
||||
return null!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub method for RoleBehaviour.HandleRoleRpc.
|
||||
/// </summary>
|
||||
/// <param name="instance">The RoleBehaviour object.</param>
|
||||
[HarmonyReversePatch]
|
||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.HandleRoleRpc))]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void HandleRoleRpc(RoleBehaviour instance, byte callId, MessageReader reader)
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Stub method for RoleBehaviour.KillAnimSpecialSetup.
|
||||
/// </summary>
|
||||
/// <param name="instance">The RoleBehaviour object.</param>
|
||||
[HarmonyReversePatch]
|
||||
[HarmonyPatch(typeof(RoleBehaviour), nameof(RoleBehaviour.KillAnimSpecialSetup))]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
public static void KillAnimSpecialSetup(RoleBehaviour instance, DeadBody deadBody, PlayerControl killer, PlayerControl victim)
|
||||
{
|
||||
// nothing needed
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,16 @@ public interface IMiraPlugin
|
||||
/// </summary>
|
||||
string OptionsTitleText { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name for the first custom category in the game options menu, if any.
|
||||
/// </summary>
|
||||
public virtual string CustomOptionMenuNameOne => "Custom Category 1";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name for the second custom category in the game options menu, if any.
|
||||
/// </summary>
|
||||
public virtual string CustomOptionMenuNameTwo => "Custom Category 2";
|
||||
|
||||
/// <summary>
|
||||
/// Gets the BepInEx configuration file for the plugin.
|
||||
/// </summary>
|
||||
|
||||
@@ -70,14 +70,14 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (!method.IsStatic)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
Error($"Event method {method.Name} in {type.Name} must be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameters = method.GetParameters();
|
||||
if (parameters.Length != 1 || !parameters[0].ParameterType.IsSubclassOf(typeof(MiraEvent)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
Error($"Invalid event registration method {method.Name} in {type.Name}");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -131,7 +131,7 @@ public sealed class MiraPluginManager
|
||||
info.SavePublicCollections();
|
||||
PresetManager.CreateDefaultPreset(info);
|
||||
PresetManager.LoadPresets(info);
|
||||
Logger<MiraApiPlugin>.Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
|
||||
Info($"Registering mod {pluginInfo.Metadata.GUID} with Mira API.");
|
||||
};
|
||||
IL2CPPChainloader.Instance.Finished += PaletteManager.RegisterAllColors;
|
||||
IL2CPPChainloader.Instance.Finished += () =>
|
||||
@@ -167,7 +167,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register game over {type.Name}: {e}");
|
||||
Error($"Failed to register game over {type.Name}: {e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -190,7 +190,7 @@ public sealed class MiraPluginManager
|
||||
{
|
||||
if (property.GetMethod?.IsStatic == true)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Option property {property.Name} in {type.Name} must not be static.");
|
||||
Error($"Option property {property.Name} in {type.Name} must not be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -211,14 +211,14 @@ public sealed class MiraPluginManager
|
||||
|
||||
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(IModdedOption))))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for options.");
|
||||
Error($"{field.Name} is a field, not a property. Use properties for options.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -235,7 +235,7 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (!ModList.GetById(pluginInfo.PluginId).IsRequiredOnAllClients)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Custom roles are only supported on all clients.");
|
||||
Error("Custom roles are only supported on all clients.");
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register role for {type.Name}: {e}");
|
||||
Error($"Failed to register role for {type.Name}: {e}");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -260,7 +260,7 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (!type.IsStatic())
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Color class {type.Name} must be static.");
|
||||
Error($"Color class {type.Name} must be static.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -273,7 +273,7 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (property.GetValue(null) is not CustomColor color)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Color property {property.Name} in {type.Name} is not a CustomColor.");
|
||||
Error($"Color property {property.Name} in {type.Name} is not a CustomColor.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -282,12 +282,12 @@ public sealed class MiraPluginManager
|
||||
|
||||
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(CustomColor))))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for colors.");
|
||||
Error($"{field.Name} is a field, not a property. Use properties for colors.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register color class {type.Name}: {e}");
|
||||
Error($"Failed to register color class {type.Name}: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register modifier {type.Name}: {e}");
|
||||
Error($"Failed to register modifier {type.Name}: {e}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -312,7 +312,7 @@ public sealed class MiraPluginManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register button {type.Name}: {e}");
|
||||
Error($"Failed to register button {type.Name}: {e}");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -355,13 +355,13 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (property.GetMethod?.IsStatic == true)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Option property {property.Name} in {type.Name} must not be static.");
|
||||
Error($"Option property {property.Name} in {type.Name} must not be static.");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property.GetValue(tabInstance) is not ConfigEntryBase configEntry)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Option property {property.Name} in {type.Name} has to be a config entry.");
|
||||
Error($"Option property {property.Name} in {type.Name} has to be a config entry.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -376,14 +376,14 @@ public sealed class MiraPluginManager
|
||||
|
||||
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(ConfigEntryBase)) && f.GetCustomAttribute<LocalSettingAttribute>() != null))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for local settings.");
|
||||
Error($"{field.Name} is a field, not a property. Use properties for local settings.");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
Error($"Failed to register options for {type.Name}: {e.ToString()}");
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -400,7 +400,7 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (!type.IsStatic())
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Keybinds class {type.Name} must be static.");
|
||||
Error($"Keybinds class {type.Name} must be static.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -413,7 +413,7 @@ public sealed class MiraPluginManager
|
||||
|
||||
if (property.GetValue(null) is not MiraKeybind keybind)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Keybind property {property.Name} in {type.Name} is not a MiraKeybind.");
|
||||
Error($"Keybind property {property.Name} in {type.Name} is not a MiraKeybind.");
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -430,12 +430,12 @@ public sealed class MiraPluginManager
|
||||
|
||||
foreach (var field in type.GetFields().Where(f => f.FieldType.IsAssignableTo(typeof(MiraKeybind))))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{field.Name} is a field, not a property. Use properties for keybinds.");
|
||||
Error($"{field.Name} is a field, not a property. Use properties for keybinds.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to register keybind class {type.Name}: {e}");
|
||||
Error($"Failed to register keybind class {type.Name}: {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public static class PresetManager
|
||||
foreach (var file in Directory.GetFiles(pluginPresetPath, "*.cfg"))
|
||||
{
|
||||
var fileName = Path.GetFileName(file);
|
||||
Logger<MiraApiPlugin>.Info($"Loading preset file {fileName}");
|
||||
Info($"Loading preset file {fileName}");
|
||||
var presetName = Path.GetFileNameWithoutExtension(file);
|
||||
var presetConfig = new ConfigFile(file, false)
|
||||
{
|
||||
|
||||
Binary file not shown.
@@ -34,8 +34,8 @@ public record struct CustomRoleConfiguration
|
||||
ShowInFreeplay = roleBehaviour?.IsDead == false;
|
||||
FreeplayFolder = role.Team switch
|
||||
{
|
||||
ModdedRoleTeams.Crewmate => "Crewmate",
|
||||
ModdedRoleTeams.Impostor => "Impostor",
|
||||
ModdedRoleTeams.Crewmate => TranslationController.Instance.GetString(StringNames.Crewmate),
|
||||
ModdedRoleTeams.Impostor => TranslationController.Instance.GetString(StringNames.Impostor),
|
||||
_ => "Neutral",
|
||||
};
|
||||
IntroSound = role.Team is ModdedRoleTeams.Crewmate
|
||||
|
||||
@@ -4,6 +4,7 @@ using System.Linq;
|
||||
using AmongUs.GameOptions;
|
||||
using Il2CppInterop.Runtime;
|
||||
using Il2CppInterop.Runtime.Injection;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.Networking;
|
||||
using MiraAPI.PluginLoading;
|
||||
using MiraAPI.Utilities;
|
||||
@@ -53,6 +54,9 @@ public static class CustomRoleManager
|
||||
internal static readonly Dictionary<ushort, RoleBehaviour> CustomRoles = [];
|
||||
internal static readonly Dictionary<Type, ushort> RoleIds = [];
|
||||
|
||||
private static Il2CppSystem.Collections.Generic.List<BaseGameSetting>? _emptySettings;
|
||||
private static Il2CppReferenceArray<OverlayKillAnimation>? _emptyKillAnimations;
|
||||
|
||||
private static ushort _roleId = 100;
|
||||
|
||||
private static ushort GetNextRoleId()
|
||||
@@ -104,7 +108,7 @@ public static class CustomRoleManager
|
||||
{
|
||||
if (!(typeof(RoleBehaviour).IsAssignableFrom(roleType) && typeof(ICustomRole).IsAssignableFrom(roleType)))
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"{roleType.Name} does not inherit from RoleBehaviour or ICustomRole.");
|
||||
Error($"{roleType.Name} does not inherit from RoleBehaviour or ICustomRole.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -133,6 +137,12 @@ public static class CustomRoleManager
|
||||
roleBehaviour.MaxCount = customRole.Configuration.MaxRoleCount;
|
||||
roleBehaviour.RoleScreenshot = customRole.Configuration.OptionsScreenshot?.LoadAsset();
|
||||
|
||||
_emptySettings ??= new(0);
|
||||
_emptyKillAnimations ??= new(0);
|
||||
|
||||
roleBehaviour.AllGameSettings = _emptySettings;
|
||||
roleBehaviour.CustomKillAnimations = _emptyKillAnimations;
|
||||
|
||||
if (customRole.Configuration.Icon != null)
|
||||
{
|
||||
var asset = customRole.Configuration.Icon.LoadAsset();
|
||||
@@ -158,7 +168,7 @@ public static class CustomRoleManager
|
||||
|
||||
if (useTaskHint && !overridesTaskText)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Role {customRole.RoleName} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!");
|
||||
Error($"Role {customRole.RoleName} is using RoleHintType.TaskHint but does not override SpawnTaskHeader!");
|
||||
}
|
||||
|
||||
CustomRoles.Add(roleId, roleBehaviour);
|
||||
@@ -288,7 +298,7 @@ public static class CustomRoleManager
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error(e);
|
||||
Error(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public interface ICustomRole : IOptionable
|
||||
{
|
||||
if (!Configuration.CanModifyChance)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Cannot modify chance for role: {RoleName}");
|
||||
Error($"Cannot modify chance for role: {RoleName}");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ public interface ICustomRole : IOptionable
|
||||
return;
|
||||
}
|
||||
|
||||
Logger<MiraApiPlugin>.Error($"Error getting chance configuration for role: {RoleName}");
|
||||
Error($"Error getting chance configuration for role: {RoleName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -181,7 +181,7 @@ public interface ICustomRole : IOptionable
|
||||
return;
|
||||
}
|
||||
|
||||
Logger<MiraApiPlugin>.Error($"Error getting count configuration for role: {RoleName}");
|
||||
Error($"Error getting count configuration for role: {RoleName}");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -33,7 +33,7 @@ public static class AddressablesLoader
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more catalogs.");
|
||||
Error("AddressablesLoader has already been initialized, cannot register more catalogs.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ public static class AddressablesLoader
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for hats.");
|
||||
Error("AddressablesLoader has already been initialized, cannot register more keys for hats.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public static class AddressablesLoader
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for skins.");
|
||||
Error("AddressablesLoader has already been initialized, cannot register more keys for skins.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ public static class AddressablesLoader
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for nameplates.");
|
||||
Error("AddressablesLoader has already been initialized, cannot register more keys for nameplates.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ public static class AddressablesLoader
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized, cannot register more keys for visors.");
|
||||
Error("AddressablesLoader has already been initialized, cannot register more keys for visors.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,7 +106,7 @@ public static class AddressablesLoader
|
||||
{
|
||||
if (_isInitialized)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("AddressablesLoader has already been initialized.");
|
||||
Error("AddressablesLoader has already been initialized.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,10 +129,10 @@ public static class AddressablesLoader
|
||||
// Check for errors
|
||||
if (catalogOperation.Status != AsyncOperationStatus.Succeeded)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to load catalog {location}.");
|
||||
Error($"Failed to load catalog {location}.");
|
||||
}
|
||||
|
||||
Logger<MiraApiPlugin>.Info($"Loaded addressables {location}.");
|
||||
Info($"Loaded addressables {location}.");
|
||||
LoadedLocations.Add(location);
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ public static class AddressablesLoader
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to find tag {tag}");
|
||||
Error($"Failed to find tag {tag}");
|
||||
}
|
||||
}
|
||||
return behaviours;
|
||||
@@ -214,7 +214,7 @@ public static class AddressablesLoader
|
||||
}
|
||||
catch
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error($"Failed to find tag {tag}");
|
||||
Error($"Failed to find tag {tag}");
|
||||
}
|
||||
}
|
||||
return behaviours;
|
||||
|
||||
@@ -38,7 +38,7 @@ public class LoadableAddressableAsset<T>(string uid) : LoadableAsset<T> where T
|
||||
|
||||
var remoteReference = new AssetReference(uid);
|
||||
|
||||
if (!GC.TryStartNoGCRegion(4096, true)) Logger<MiraApiPlugin>.Error("Could not start NoGCRegion of size 4kb, there is the possibility of injected unmanaged classes being garbage collected as per BepInEx/Il2CppInterop/issues/40");
|
||||
if (!GC.TryStartNoGCRegion(4096, true)) Error("Could not start NoGCRegion of size 4kb, there is the possibility of injected unmanaged classes being garbage collected as per BepInEx/Il2CppInterop/issues/40");
|
||||
|
||||
LoadedAsset = remoteReference.LoadAssetAsync<T>().WaitForCompletion();
|
||||
if (LoadedAsset == null)
|
||||
|
||||
@@ -45,7 +45,7 @@ public class LoadableAddressableAssets<T>(string key) where T : UnityEngine.Obje
|
||||
|
||||
var locations = Addressables.LoadResourceLocationsAsync(key).WaitForCompletion();
|
||||
|
||||
if (!GC.TryStartNoGCRegion(4096, true)) Logger<MiraApiPlugin>.Error("Could not start NoGCRegion of size 4kb, there is the possibility of injected unmanaged classes being garbage collected as per BepInEx/Il2CppInterop/issues/40");
|
||||
if (!GC.TryStartNoGCRegion(4096, true)) Error("Could not start NoGCRegion of size 4kb, there is the possibility of injected unmanaged classes being garbage collected as per BepInEx/Il2CppInterop/issues/40");
|
||||
|
||||
var assetsIList = Addressables.LoadAssetsAsync<T>(locations, null, false).WaitForCompletion();
|
||||
if (assetsIList == null)
|
||||
|
||||
@@ -12,7 +12,7 @@ public static class MiraAssets
|
||||
{
|
||||
var boxTex = SpriteTools.LoadTextureFromResourcePath(
|
||||
"MiraAPI.Resources.RoundedBox.png",
|
||||
System.Reflection.Assembly.GetCallingAssembly());
|
||||
System.Reflection.Assembly.GetExecutingAssembly());
|
||||
var boxSprite = Sprite.Create(
|
||||
boxTex,
|
||||
new Rect(0, 0, boxTex.width, boxTex.height),
|
||||
@@ -109,12 +109,12 @@ public static class MiraAssets
|
||||
/// Gets the sprite used for crewmate role files in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset CrewmateFile { get; } = new("MiraAPI.Resources.CrewmateFile.png");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for impostor role files in TaskAdderGame.
|
||||
/// </summary>
|
||||
public static LoadableResourceAsset ImpostorFile { get; } = new("MiraAPI.Resources.ImpostorFile.png");
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets the sprite used for custom team role files in TaskAdderGame.
|
||||
/// </summary>
|
||||
|
||||
@@ -19,7 +19,10 @@ public static class SpriteTools
|
||||
/// <exception cref="ArgumentException">Thrown when the resource cannot be found in the specified assembly.</exception>
|
||||
public static Texture2D LoadTextureFromResourcePath(string resourcePath, Assembly assembly)
|
||||
{
|
||||
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false);
|
||||
var tex = new Texture2D(1, 1, TextureFormat.ARGB32, false)
|
||||
{
|
||||
wrapMode = TextureWrapMode.Clamp,
|
||||
};
|
||||
var myStream = assembly.GetManifestResourceStream(resourcePath);
|
||||
if (myStream != null)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
/// <summary>
|
||||
/// Enum serializer for converting enums to and from byte arrays.
|
||||
/// </summary>
|
||||
public static class EnumSerializer
|
||||
{
|
||||
/// <summary>
|
||||
/// Serializes an enum to an array of bytes.
|
||||
/// </summary>
|
||||
/// <param name="value">The enum value to serialize.</param>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
/// <returns>A byte array representing the enum value.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
|
||||
public static byte[] EnumToBytes<T>(T value) where T : struct, Enum
|
||||
{
|
||||
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||
|
||||
return underlyingType switch
|
||||
{
|
||||
not null when underlyingType == typeof(byte) => [(byte)(object)value],
|
||||
not null when underlyingType == typeof(sbyte) => [(byte)(sbyte)(object)value],
|
||||
not null when underlyingType == typeof(short) => BitConverter.GetBytes((short)(object)value),
|
||||
not null when underlyingType == typeof(ushort) => BitConverter.GetBytes((ushort)(object)value),
|
||||
not null when underlyingType == typeof(int) => BitConverter.GetBytes((int)(object)value),
|
||||
not null when underlyingType == typeof(uint) => BitConverter.GetBytes((uint)(object)value),
|
||||
not null when underlyingType == typeof(long) => BitConverter.GetBytes((long)(object)value),
|
||||
not null when underlyingType == typeof(ulong) => BitConverter.GetBytes((ulong)(object)value),
|
||||
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts bytes to an enum using the underlying type.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte array to convert.</param>
|
||||
/// <param name="offset">The offset in the byte array to start reading from.</param>
|
||||
/// <param name="underlyingType">The underlying type of the enum.</param>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
/// <returns>The enum value represented by the bytes.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the underlying type is not recognized.</exception>
|
||||
public static T EnumFromBytes<T>(byte[] bytes, int offset, Type underlyingType) where T : struct, Enum
|
||||
{
|
||||
return underlyingType switch
|
||||
{
|
||||
{ } t when t == typeof(byte) || t == typeof(sbyte) => (T)(object)bytes[offset],
|
||||
{ } t when t == typeof(short) => (T)(object)BitConverter.ToInt16(bytes, offset),
|
||||
{ } t when t == typeof(ushort) => (T)(object)BitConverter.ToUInt16(bytes, offset),
|
||||
{ } t when t == typeof(int) => (T)(object)BitConverter.ToInt32(bytes, offset),
|
||||
{ } t when t == typeof(uint) => (T)(object)BitConverter.ToUInt32(bytes, offset),
|
||||
{ } t when t == typeof(long) => (T)(object)BitConverter.ToInt64(bytes, offset),
|
||||
{ } t when t == typeof(ulong) => (T)(object)BitConverter.ToUInt64(bytes, offset),
|
||||
_ => throw new ArgumentException("Unknown underlying type for " + typeof(T).Name),
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="EnumFromBytes{T}(byte[], int, Type)"/>
|
||||
public static T EnumFromBytes<T>(byte[] bytes, int offset) where T : struct, Enum => EnumFromBytes<T>(bytes, offset, typeof(T).GetEnumUnderlyingType());
|
||||
|
||||
/// <summary>
|
||||
/// Converts a byte array into a collection of enum values.
|
||||
/// </summary>
|
||||
/// <param name="bytes">The byte array to convert.</param>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
/// <returns>An enumerable collection of enum values.</returns>
|
||||
/// <exception cref="ArgumentException">Thrown when the byte array length is not a multiple of the enum's underlying type size.</exception>
|
||||
public static IEnumerable<T> EnumsFromBytes<T>(byte[] bytes) where T : struct, Enum
|
||||
{
|
||||
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||
var size = GetManagedSize(underlyingType);
|
||||
|
||||
if (bytes.Length % size != 0)
|
||||
{
|
||||
throw new ArgumentException("Byte array length must be a multiple of " + size + ".");
|
||||
}
|
||||
|
||||
return EnumsFromBytesIterator<T>(bytes, underlyingType, size);
|
||||
}
|
||||
|
||||
private static IEnumerable<T> EnumsFromBytesIterator<T>(byte[] bytes, Type underlyingType, int size) where T : struct, Enum
|
||||
{
|
||||
var count = bytes.Length / size;
|
||||
var underlyingArray = Array.CreateInstance(underlyingType, count);
|
||||
Buffer.BlockCopy(bytes, 0, underlyingArray, 0, bytes.Length);
|
||||
|
||||
foreach (var value in underlyingArray)
|
||||
yield return (T)value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts a collection of enum values to a byte array.
|
||||
/// </summary>
|
||||
/// <param name="values"> The collection of enum values to convert.</param>
|
||||
/// <typeparam name="T">The enum type.</typeparam>
|
||||
/// <returns>A byte array representing the collection of enum values.</returns>
|
||||
public static byte[] EnumsToBytes<T>(IEnumerable<T> values) where T : struct, Enum
|
||||
{
|
||||
var underlyingType = typeof(T).GetEnumUnderlyingType();
|
||||
var size = GetManagedSize(underlyingType);
|
||||
var valuesArray = values.ToArray();
|
||||
var count = valuesArray.Length;
|
||||
|
||||
var underlyingArray = Array.CreateInstance(underlyingType, count);
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
underlyingArray.SetValue(ConvertToUnderlyingType(valuesArray[i], underlyingType), i);
|
||||
}
|
||||
|
||||
var bytes = new byte[count * size];
|
||||
Buffer.BlockCopy(underlyingArray, 0, bytes, 0, bytes.Length);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
private static object ConvertToUnderlyingType<T>(T value, Type underlyingType) where T : struct, Enum
|
||||
{
|
||||
return underlyingType switch
|
||||
{
|
||||
not null when underlyingType == typeof(byte) => (byte)(object)value,
|
||||
not null when underlyingType == typeof(sbyte) => (sbyte)(object)value,
|
||||
not null when underlyingType == typeof(short) => (short)(object)value,
|
||||
not null when underlyingType == typeof(ushort) => (ushort)(object)value,
|
||||
not null when underlyingType == typeof(int) => (int)(object)value,
|
||||
not null when underlyingType == typeof(uint) => (uint)(object)value,
|
||||
not null when underlyingType == typeof(long) => (long)(object)value,
|
||||
not null when underlyingType == typeof(ulong) => (ulong)(object)value,
|
||||
_ => throw new InvalidOperationException("Unsupported underlying type."),
|
||||
};
|
||||
}
|
||||
|
||||
private static int GetManagedSize(Type type)
|
||||
{
|
||||
return Type.GetTypeCode(type) switch
|
||||
{
|
||||
TypeCode.Byte or TypeCode.SByte => 1,
|
||||
TypeCode.Int16 or TypeCode.UInt16 => 2,
|
||||
TypeCode.Int32 or TypeCode.UInt32 => 4,
|
||||
TypeCode.Int64 or TypeCode.UInt64 => 8,
|
||||
_ => throw new ArgumentException("Unsupported underlying type: " + type.Name),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -29,13 +29,13 @@ public static class Extensions
|
||||
|
||||
if (count == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Couldn't get role count for NetData, defaulting to zero.");
|
||||
Error("Couldn't get role count for NetData, defaulting to zero.");
|
||||
count = 0;
|
||||
}
|
||||
|
||||
if (chance == null)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Error("Couldn't get role chance for NetData, defaulting to zero.");
|
||||
Error("Couldn't get role chance for NetData, defaulting to zero.");
|
||||
chance = 0;
|
||||
}
|
||||
|
||||
@@ -398,7 +398,7 @@ public static class Extensions
|
||||
|
||||
if (length > chunkSize)
|
||||
{
|
||||
Logger<MiraApiPlugin>.Info($"NetData length is greater than chunk size: {length} > {chunkSize}");
|
||||
Info($"NetData length is greater than chunk size: {length} > {chunkSize}");
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,13 +3,17 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Il2CppInterop.Runtime.InteropTypes.Arrays;
|
||||
using MiraAPI.Keybinds;
|
||||
using MiraAPI.Roles;
|
||||
using MiraAPI.Utilities.Assets;
|
||||
using Reactor.Utilities;
|
||||
using Rewired;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using MethodBase = System.Reflection.MethodBase;
|
||||
using Object = UnityEngine.Object;
|
||||
using Random = UnityEngine.Random;
|
||||
|
||||
namespace MiraAPI.Utilities;
|
||||
|
||||
@@ -37,6 +41,31 @@ public static class Helpers
|
||||
return keybindIcon;
|
||||
}
|
||||
|
||||
public static MethodBase? GetStateMachineMoveNext<T>(string methodName)
|
||||
{
|
||||
var typeName = typeof(T).FullName;
|
||||
var showRoleStateMachine =
|
||||
typeof(T)
|
||||
.GetNestedTypes()
|
||||
.FirstOrDefault(x=>x.Name.Contains(methodName));
|
||||
|
||||
if (showRoleStateMachine == null)
|
||||
{
|
||||
Error($"Failed to find {methodName} state machine for {typeName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var moveNext = AccessTools.Method(showRoleStateMachine, "MoveNext");
|
||||
if (moveNext == null)
|
||||
{
|
||||
Error($"Failed to find MoveNext method for {typeName}.{methodName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
Info($"Found {methodName}.MoveNext");
|
||||
return moveNext;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a draggable scroller. Add items into the scroller.Inner transform. This does not automatically position children.
|
||||
/// </summary>
|
||||
@@ -112,7 +141,7 @@ public static class Helpers
|
||||
return true;
|
||||
default:
|
||||
{
|
||||
var num = UnityEngine.Random.RandomRangeInt(1, 101);
|
||||
var num = Random.RandomRangeInt(1, 101);
|
||||
return num <= probability;
|
||||
}
|
||||
}
|
||||
@@ -473,7 +502,7 @@ public static class Helpers
|
||||
public static string RandomString(int length, string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
{
|
||||
return new string(Enumerable.Repeat(chars, length)
|
||||
.Select(s => s[UnityEngine.Random.RandomRangeInt(0, s.Length)]).ToArray());
|
||||
.Select(s => s[Random.RandomRangeInt(0, s.Length)]).ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -495,4 +524,91 @@ public static class Helpers
|
||||
(Il2CppSystem.Object[])[value.ToString(formatString, CultureInfo.InvariantCulture)]),
|
||||
};
|
||||
}
|
||||
/// <summary>
|
||||
/// Shorthand for <see cref="string.Split(char, StringSplitOptions)"/> that removes empty entries and trims leading and ending spaces.
|
||||
/// </summary>
|
||||
/// <param name="value">The string to split.</param>
|
||||
/// <param name="separator">The separator.</param>
|
||||
/// <returns>An array of substrings.</returns>
|
||||
public static string[] TrueSplit(this string value, char separator) => value.Split(separator, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
|
||||
/// <summary>
|
||||
/// Joins a collection of items that are separated by a character.
|
||||
/// </summary>
|
||||
/// <param name="separator">The separator.</param>
|
||||
/// <param name="items">The collection of items to string together.</param>
|
||||
/// <typeparam name="T">The type of the items.</typeparam>
|
||||
/// <returns>A string formed by concatenating the ToString results of each item, each separated by a separator.</returns>
|
||||
public static string Join<T>(char separator, IEnumerable<T> items) => Join(separator.ToString(), items);
|
||||
|
||||
/// <inheritdoc cref="Join{T}(char, IEnumerable{T})"/>
|
||||
public static string Join<T>(string separator, IEnumerable<T> items)
|
||||
{
|
||||
if (!items.Any())
|
||||
return string.Empty;
|
||||
|
||||
var result = string.Empty;
|
||||
items.Do(x => result += $"{separator}{x}");
|
||||
return result[separator.Length..];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand helper for overriding the on click listeners for PassiveButton.
|
||||
/// </summary>
|
||||
/// <param name="passive">The PassiveButton being modified.</param>
|
||||
/// <param name="action">The action that's replacing the original listeners.</param>
|
||||
public static void OverrideOnClickListeners(this PassiveButton passive, Action action)
|
||||
{
|
||||
if (!passive)
|
||||
return;
|
||||
|
||||
passive.OnClick?.RemoveAllListeners();
|
||||
passive.OnClick = new();
|
||||
passive.OnClick.AddListener(action);
|
||||
passive.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand helper for overriding the on mouse over listeners for PassiveButton.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="OverrideOnClickListeners"/>
|
||||
public static void OverrideOnMouseOverListeners(this PassiveButton passive, Action action)
|
||||
{
|
||||
if (!passive)
|
||||
return;
|
||||
|
||||
passive.OnMouseOver?.RemoveAllListeners();
|
||||
passive.OnMouseOver = new();
|
||||
passive.OnMouseOver.AddListener(action);
|
||||
passive.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shorthand helper for overriding the on mouse out listeners for PassiveButton.
|
||||
/// </summary>
|
||||
/// <inheritdoc cref="OverrideOnClickListeners"/>
|
||||
public static void OverrideOnMouseOutListeners(this PassiveButton passive, Action action)
|
||||
{
|
||||
if (!passive)
|
||||
return;
|
||||
|
||||
passive.OnMouseOut?.RemoveAllListeners();
|
||||
passive.OnMouseOut = new();
|
||||
passive.OnMouseOut.AddListener(action);
|
||||
passive.enabled = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the string of a role.
|
||||
/// </summary>
|
||||
/// <param name="role">The role to find.</param>
|
||||
/// <returns>The role name.</returns>
|
||||
public static string GetRoleName(this RoleBehaviour role)
|
||||
{
|
||||
if (role is ICustomRole custom)
|
||||
{
|
||||
return custom.RoleName;
|
||||
}
|
||||
return role.NiceName;
|
||||
}
|
||||
}
|
||||
@@ -24,4 +24,9 @@ public enum MiraNumberSuffixes
|
||||
/// Percentage suffix (%).
|
||||
/// </summary>
|
||||
Percent,
|
||||
|
||||
/// <summary>
|
||||
/// Display in units.
|
||||
/// </summary>
|
||||
Distance,
|
||||
}
|
||||
Loaded 100 of 105 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user