Add custom options menu

This commit is contained in:
Ape committed 2025-12-05 22:37:51 +01:00
1 parent 0979589ca3
commit 0a55c35108
15 files changed
+1756 -4

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
using System.Threading.Tasks;
using HarmonyLib;
using UnityEngine;
using System.Collections.Generic;
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionHolder.cs
namespace HNSRevamped
{
[HarmonyPatch]
public static class Options
{
static Task taskOptionsLoad;
[HarmonyPatch(typeof(TranslationController), nameof(TranslationController.Initialize)), HarmonyPostfix]
public static void OptionsLoadStart()
{
taskOptionsLoad = Task.Run(Load);
}
[HarmonyPatch(typeof(MainMenuManager), nameof(MainMenuManager.Start)), HarmonyPostfix]
public static void WaitOptionsLoad()
{
taskOptionsLoad.Wait();
}
public static OptionItem Testy;
public static OptionItem Testy1;
public static OptionItem Testy2;
public static bool IsLoaded = false;
public static void Load()
{
if (IsLoaded) return;
//Main settings
Testy = IntegerOptionItem.Create(3, "Testy", new IntegerValueRule(0, 50, 5), 10, TabGroup.GameModifiers, false);
Testy1 = BooleanOptionItem.Create(4, "Testy1", true, TabGroup.ModSettings, false)
.SetColor(Color.red);
Testy2 = BooleanOptionItem.Create(5, "Testy2", true, TabGroup.ModSettings, false)
.SetParent(Testy1);
IsLoaded = true;
}
}
}
+71
View File
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using AmongUs.GameOptions;
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionBackup/OptionBackupData.cs
namespace HNSRevamped
{
public class OptionBackupData
{
public List<OptionBackupValue> AllValues;
public OptionBackupData(IGameOptions option)
{
AllValues = new(32);
foreach (ByteOptionNames name in Enum.GetValues(typeof(ByteOptionNames)))
{
if (option.TryGetByte(name, out var value))
AllValues.Add(new ByteOptionBackupValue(name, value));
}
foreach (BoolOptionNames name in Enum.GetValues(typeof(BoolOptionNames)))
{
if (name == BoolOptionNames.GhostsDoTasks) continue;
if (option.TryGetBool(name, out var value))
AllValues.Add(new BoolOptionBackupValue(name, value));
}
foreach (FloatOptionNames name in Enum.GetValues(typeof(FloatOptionNames)))
{
if (option.TryGetFloat(name, out var value))
AllValues.Add(new FloatOptionBackupValue(name, value));
}
foreach (Int32OptionNames name in Enum.GetValues(typeof(Int32OptionNames)))
{
if (option.TryGetInt(name, out var value))
AllValues.Add(new IntOptionBackupValue(name, value));
}
AllValues.Add(new IntOptionBackupValue(Int32OptionNames.MaxPlayers, option.MaxPlayers));
AllValues.Add(new UIntOptionBackupValue(UInt32OptionNames.Keywords, (uint)option.Keywords));
foreach (RoleTypes role in new RoleTypes[] { RoleTypes.Scientist, RoleTypes.Engineer, RoleTypes.GuardianAngel, RoleTypes.Shapeshifter, RoleTypes.Noisemaker, RoleTypes.Phantom, RoleTypes.Tracker, RoleTypes.Detective, RoleTypes.Viper })
{
AllValues.Add(new RoleRateBackupValue(role, option.RoleOptions.GetNumPerGame(role), option.RoleOptions.GetChancePerGame(role)));
}
}
public IGameOptions Restore(IGameOptions option)
{
foreach (var value in AllValues)
{
value.Restore(option);
}
return option;
}
public byte GetByte(ByteOptionNames name) => Get<ByteOptionNames, byte>(name);
public bool GetBool(BoolOptionNames name) => Get<BoolOptionNames, bool>(name);
public float GetFloat(FloatOptionNames name) => Get<FloatOptionNames, float>(name);
public int GetInt(Int32OptionNames name) => Get<Int32OptionNames, int>(name);
public uint GetUInt(UInt32OptionNames name) => Get<UInt32OptionNames, uint>(name);
public TValue Get<TKey, TValue>(TKey name)
where TKey : Enum
{
var value = AllValues
.OfType<OptionBackupValueBase<TKey, TValue>>()
.Where(val => val.OptionName.Equals(name)).
FirstOrDefault();
return value == null ? default : value.Value;
}
}
}
+82
View File
@@ -0,0 +1,82 @@
using System;
using AmongUs.GameOptions;
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionBackup/OptionBackupValue.cs
namespace HNSRevamped
{
public abstract class OptionBackupValue
{
public abstract void Restore(IGameOptions option);
}
public abstract class OptionBackupValueBase<NameT, ValueT> : OptionBackupValue
where NameT : Enum
{
public readonly NameT OptionName;
public readonly ValueT Value;
public OptionBackupValueBase(NameT name, ValueT value)
{
OptionName = name;
Value = value;
}
}
public class ByteOptionBackupValue : OptionBackupValueBase<ByteOptionNames, byte>
{
public ByteOptionBackupValue(ByteOptionNames name, byte value) : base(name, value) { }
public override void Restore(IGameOptions option)
{
option.SetByte(OptionName, Value);
}
}
public class BoolOptionBackupValue : OptionBackupValueBase<BoolOptionNames, bool>
{
public BoolOptionBackupValue(BoolOptionNames name, bool value) : base(name, value) { }
public override void Restore(IGameOptions option)
{
option.SetBool(OptionName, Value);
}
}
public class FloatOptionBackupValue : OptionBackupValueBase<FloatOptionNames, float>
{
public FloatOptionBackupValue(FloatOptionNames name, float value) : base(name, value) { }
public override void Restore(IGameOptions option)
{
option.SetFloat(OptionName, Value);
}
}
public class IntOptionBackupValue : OptionBackupValueBase<Int32OptionNames, int>
{
public IntOptionBackupValue(Int32OptionNames name, int value) : base(name, value) { }
public override void Restore(IGameOptions option)
{
option.SetInt(OptionName, Value);
}
}
public class UIntOptionBackupValue : OptionBackupValueBase<UInt32OptionNames, uint>
{
public UIntOptionBackupValue(UInt32OptionNames name, uint value) : base(name, value) { }
public override void Restore(IGameOptions option)
{
option.SetUInt(OptionName, Value);
}
}
public class RoleRateBackupValue : OptionBackupValue
{
public RoleTypes roleType;
public int maxCount;
public int chance;
public RoleRateBackupValue(RoleTypes type, int maxCount, int chance)
{
this.roleType = type;
this.maxCount = maxCount;
this.chance = chance;
}
public override void Restore(IGameOptions option)
{
option.RoleOptions.SetRoleRate(roleType, maxCount, chance);
}
}
}
+29
View File
@@ -0,0 +1,29 @@
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionItems/BooleanOptionItem.cs
namespace HNSRevamped
{
public class BooleanOptionItem : OptionItem
{
public BooleanOptionItem(int id, string name, bool defaultValue, TabGroup tab, bool isSingleValue)
: base(id, name, defaultValue ? 1 : 0, tab, isSingleValue)
{
}
public static BooleanOptionItem Create(
int id, string name, bool defaultValue, TabGroup tab, bool isSingleValue
)
{
return new BooleanOptionItem(
id, name, defaultValue, tab, isSingleValue
);
}
public override string GetString()
{
return GetBool() ? "✓" : "X";
}
public override void SetValue(int value)
{
base.SetValue(value % 2 == 0 ? 0 : 1);
}
}
}
+36
View File
@@ -0,0 +1,36 @@
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionItems/FloatOptionItem.cs
namespace HNSRevamped
{
public class FloatOptionItem : OptionItem
{
public FloatValueRule Rule;
public FloatOptionItem(int id, string name, float defaultValue, TabGroup tab, bool isSingleValue, FloatValueRule rule)
: base(id, name, rule.GetNearestIndex(defaultValue), tab, isSingleValue)
{
Rule = rule;
}
public static FloatOptionItem Create(
int id, string name, FloatValueRule rule, float defaultValue, TabGroup tab, bool isSingleValue
)
{
return new FloatOptionItem(
id, name, defaultValue, tab, isSingleValue, rule
);
}
public override int GetInt() => (int)Rule.GetValueByIndex(CurrentValue);
public override float GetFloat() => Rule.GetValueByIndex(CurrentValue);
public override string GetString()
{
return ApplyFormat(Rule.GetValueByIndex(CurrentValue).ToString());
}
public override int GetValue()
=> Rule.RepeatIndex(base.GetValue());
public override void SetValue(int value)
{
base.SetValue(Rule.RepeatIndex(value));
}
}
}
+36
View File
@@ -0,0 +1,36 @@
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionItems/IntegerOptionItem.cs
namespace HNSRevamped
{
public class IntegerOptionItem : OptionItem
{
public IntegerValueRule Rule;
public IntegerOptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSingleValue, IntegerValueRule rule)
: base(id, name, rule.GetNearestIndex(defaultValue), tab, isSingleValue)
{
Rule = rule;
}
public static IntegerOptionItem Create(
int id, string name, IntegerValueRule rule, int defaultValue, TabGroup tab, bool isSingleValue
)
{
return new IntegerOptionItem(
id, name, defaultValue, tab, isSingleValue, rule
);
}
public override int GetInt() => Rule.GetValueByIndex(CurrentValue);
public override float GetFloat() => Rule.GetValueByIndex(CurrentValue);
public override string GetString()
{
return ApplyFormat(Rule.GetValueByIndex(CurrentValue).ToString());
}
public override int GetValue()
=> Rule.RepeatIndex(base.GetValue());
public override void SetValue(int value)
{
base.SetValue(Rule.RepeatIndex(value));
}
}
}
+229
View File
@@ -0,0 +1,229 @@
using System;
using System.Collections.Generic;
using BepInEx.Configuration;
using UnityEngine;
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionItems/OptionItem.cs
namespace HNSRevamped
{
public abstract class OptionItem
{
public static IReadOnlyList<OptionItem> AllOptions => _allOptions;
private static List<OptionItem> _allOptions = new();
public static int CurrentPreset { get; set; }
public int Id { get; }
public string Name { get; }
public int DefaultValue { get; }
public TabGroup Tab { get; }
public bool IsSingleValue { get; }
public Color NameColor { get; protected set; }
public OptionFormat ValueFormat { get; protected set; }
public bool IsHeader { get; protected set; }
public bool IsHidden { get; protected set; }
public bool IsText { get; protected set; }
public Dictionary<string, string> ReplacementDictionary
{
get => _replacementDictionary;
set
{
if (value == null) _replacementDictionary?.Clear();
else _replacementDictionary = value;
}
}
private Dictionary<string, string> _replacementDictionary;
public int CurrentValue
{
get => GetValue();
set => SetValue(value);
}
public OptionItem Parent { get; private set; }
public List<OptionItem> Children;
public ConfigEntry<int> CurrentEntry =>
IsSingleValue ? singleEntry : AllConfigEntries[CurrentPreset];
private ConfigEntry<int>[] AllConfigEntries;
private ConfigEntry<int> singleEntry;
public OptionBehaviour OptionBehaviour;
public event EventHandler<UpdateValueEventArgs> UpdateValueEvent;
public OptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSingleValue)
{
Id = id;
Name = name;
DefaultValue = defaultValue;
Tab = tab;
IsSingleValue = isSingleValue;
NameColor = Color.white;
ValueFormat = OptionFormat.None;
IsHeader = false;
IsHidden = false;
IsText = false;
Children = new();
AllConfigEntries = new ConfigEntry<int>[10];
if (Id == 0)
{
singleEntry = Main.Instance.Config.Bind("Current Preset", id.ToString(), DefaultValue);
CurrentPreset = singleEntry.Value;
}
else if (IsSingleValue)
{
singleEntry = Main.Instance.Config.Bind("SingleEntryOptions", id.ToString(), DefaultValue);
}
else
{
for (int i = 0; i < 1; i++)
{
AllConfigEntries[i] = Main.Instance.Config.Bind($"Preset{i + 1}", id.ToString(), DefaultValue);
}
}
_allOptions.Add(this);
}
public OptionItem Do(Action<OptionItem> action)
{
action(this);
return this;
}
public OptionItem SetColor(Color value) => Do(i => i.NameColor = value);
public OptionItem SetValueFormat(OptionFormat value) => Do(i => i.ValueFormat = value);
public OptionItem SetHeader(bool value) => Do(i => i.IsHeader = value);
public OptionItem SetHidden(bool value) => Do(i => i.IsHidden = value);
public OptionItem SetText(bool value) => Do(i => i.IsHidden = value);
public OptionItem SetParent(OptionItem parent) => Do(i =>
{
i.Parent = parent;
parent.SetChild(i);
});
public OptionItem SetChild(OptionItem child) => Do(i => i.Children.Add(child));
public OptionItem RegisterUpdateValueEvent(EventHandler<UpdateValueEventArgs> handler)
=> Do(i => UpdateValueEvent += handler);
public OptionItem AddReplacement((string key, string value) kvp)
=> Do(i =>
{
ReplacementDictionary ??= new();
ReplacementDictionary.Add(kvp.key, kvp.value);
});
public OptionItem RemoveReplacement(string key)
=> Do(i => ReplacementDictionary?.Remove(key));
public virtual string GetName(bool disableColor = false)
{
return disableColor ?
Name :
Utils.ColorString(NameColor, Name);
}
public virtual bool GetBool() => CurrentValue != 0 && (Parent == null || Parent.GetBool());
public virtual int GetInt() => CurrentValue;
public virtual float GetFloat() => CurrentValue;
public virtual string GetString()
{
return ApplyFormat(CurrentValue.ToString());
}
public virtual int GetValue() => CurrentEntry.Value;
public virtual bool IsHiddenOn()
{
return IsHidden;
}
public string ApplyFormat(string value)
{
if (ValueFormat == OptionFormat.Players) return value + " players";
if (ValueFormat == OptionFormat.Seconds) return value + "s";
if (ValueFormat == OptionFormat.Percent) return value + "%";
if (ValueFormat == OptionFormat.Multiplier) return value + "x";
return value;
}
public virtual void Refresh()
{
if (OptionBehaviour is not null and StringOption opt)
{
opt.TitleText.text = GetName();
opt.ValueText.text = GetString();
opt.oldValue = opt.Value = CurrentValue;
}
}
public virtual void SetValue(int value)
{
int beforeValue = CurrentEntry.Value;
int afterValue = CurrentEntry.Value = value;
CallUpdateValueEvent(beforeValue, afterValue);
Refresh();
SyncAllOptions();
}
public static OptionItem operator ++(OptionItem item)
=> item.Do(item => item.SetValue(item.CurrentValue + 1));
public static OptionItem operator --(OptionItem item)
=> item.Do(item => item.SetValue(item.CurrentValue - 1));
public static void SwitchPreset(int newPreset)
{
CurrentPreset = Math.Clamp(newPreset, 0, 1);
foreach (var op in AllOptions)
op.Refresh();
SyncAllOptions();
}
public static void SyncAllOptions()
{
if (
PlayerControl.AllPlayerControls.Count <= 1 ||
AmongUsClient.Instance.AmHost == false ||
PlayerControl.LocalPlayer == null
) return;
GameManager.Instance.RpcSyncCustomOptions();
}
private void CallUpdateValueEvent(int beforeValue, int currentValue)
{
if (UpdateValueEvent == null) return;
UpdateValueEvent(this, new UpdateValueEventArgs(beforeValue, currentValue));
}
public class UpdateValueEventArgs : EventArgs
{
public int CurrentValue { get; set; }
public int BeforeValue { get; set; }
public UpdateValueEventArgs(int beforeValue, int currentValue)
{
CurrentValue = currentValue;
BeforeValue = beforeValue;
}
}
}
public enum TabGroup
{
ModSettings,
GameModifiers,
}
public enum OptionFormat
{
None,
Players,
Seconds,
Percent,
Multiplier,
}
}
+47
View File
@@ -0,0 +1,47 @@
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionItems/StringOptionItem.cs
namespace HNSRevamped
{
public class StringOptionItem : OptionItem
{
public IntegerValueRule Rule;
public string[] Selections;
public StringOptionItem(int id, string name, int defaultValue, TabGroup tab, bool isSingleValue, string[] selections)
: base(id, name, defaultValue, tab, isSingleValue)
{
Rule = (0, selections.Length - 1, 1);
Selections = selections;
}
public static StringOptionItem Create(
int id, string name, string[] selections, int defaultIndex, TabGroup tab, bool isSingleValue
)
{
return new StringOptionItem(
id, name, defaultIndex, tab, isSingleValue, selections
);
}
public override int GetInt() => Rule.GetValueByIndex(CurrentValue);
public override float GetFloat() => Rule.GetValueByIndex(CurrentValue);
public override string GetString()
{
return Selections[Rule.GetValueByIndex(CurrentValue)];
}
public int GetChance()
{
if (Selections.Length == 2) return CurrentValue * 100;
var offset = 12 - Selections.Length;
var index = CurrentValue + offset;
var rate = index <= 1 ? index * 5 : (index - 1) * 10;
return rate;
}
public override int GetValue()
=> Rule.RepeatIndex(base.GetValue());
public override void SetValue(int value)
{
base.SetValue(Rule.RepeatIndex(value));
}
}
}
+22
View File
@@ -0,0 +1,22 @@
// https://github.com/Yumenopai/TownOfHost_Y/blob/main/Modules/OptionItem/TextOptionItem.cs
namespace HNSRevamped
{
public class TextOptionItem : OptionItem
{
public FloatValueRule Rule;
public TextOptionItem(int id, string name, TabGroup tab)
: base(id, name, 0, tab, true)
{
IsText = true;
}
public static TextOptionItem Create(
int id, string name, TabGroup tab
)
{
return new TextOptionItem(
id, name, tab
);
}
}
}
+80
View File
@@ -0,0 +1,80 @@
using System;
// https://github.com/tukasa0001/TownOfHost/blob/main/Modules/OptionItems/ValueRule.cs
namespace HNSRevamped
{
public abstract class ValueRule<T>
{
public T MinValue { get; protected set; }
public T MaxValue { get; protected set; }
public T Step { get; protected set; }
public ValueRule(T minValue, T maxValue, T step)
{
MinValue = minValue;
MaxValue = maxValue;
Step = step;
}
public ValueRule((T, T, T) tuple)
: this(tuple.Item1, tuple.Item2, tuple.Item3)
{ }
public abstract int RepeatIndex(int value);
public abstract T GetValueByIndex(int index);
public abstract int GetNearestIndex(T num);
}
public class IntegerValueRule : ValueRule<int>
{
public IntegerValueRule(int minValue, int maxValue, int step)
: base(minValue, maxValue, step) { }
public IntegerValueRule((int, int, int) tuple)
: base(tuple) { }
public static implicit operator IntegerValueRule((int, int, int) tuple)
=> new(tuple);
public override int RepeatIndex(int value)
{
int MaxIndex = (MaxValue - MinValue) / Step;
value = value % (MaxIndex + 1);
if (value < 0) value = MaxIndex;
return value;
}
public override int GetValueByIndex(int index)
=> RepeatIndex(index) * Step + MinValue;
public override int GetNearestIndex(int num)
{
return (int)Math.Round((num - MinValue) / (float)Step);
}
}
public class FloatValueRule : ValueRule<float>
{
public FloatValueRule(float minValue, float maxValue, float step)
: base(minValue, maxValue, step) { }
public FloatValueRule((float, float, float) tuple)
: base(tuple) { }
public static implicit operator FloatValueRule((float, float, float) tuple)
=> new(tuple);
public override int RepeatIndex(int value)
{
int MaxIndex = (int)((MaxValue - MinValue) / Step);
value = value % (MaxIndex + 1);
if (value < 0) value = MaxIndex;
return value;
}
public override float GetValueByIndex(int index)
=> RepeatIndex(index) * Step + MinValue;
public override int GetNearestIndex(float num)
{
return (int)Math.Round((num - MinValue) / Step);
}
}
}
+110
View File
@@ -0,0 +1,110 @@
using HarmonyLib;
using Hazel;
using InnerNet;
using UnityEngine;
using System;
using System.Linq;
using AmongUs.GameOptions;
using Object = UnityEngine.Object;
namespace HNSRevamped
{
public enum CustomRPC
{
SyncCustomOptions,
AddCustomSettingsChangeMessage,
}
[HarmonyPatch(typeof(GameManager), nameof(GameManager.HandleRpc))]
class GameManagerHandleRpc
{
public static void Postfix(GameManager __instance, [HarmonyArgument(0)] byte callId, [HarmonyArgument(1)] MessageReader reader)
{
if (callId < 70) return;
var rpcType = (CustomRPC)callId;
switch (rpcType)
{
case CustomRPC.SyncCustomOptions:
for (int i = reader.ReadInt32(); i < OptionItem.AllOptions.Count; ++i)
{
if (reader.BytesRemaining == 0) break;
var co = OptionItem.AllOptions[i];
if (co is TextOptionItem) continue;
if (co.Id == 0)
{
co.SetValue(9);
continue;
}
if (co.Id >= 1000 && co.IsHiddenOn()) continue;
co.SetValue(reader.ReadInt32());
}
var viewSettingsPane = Object.FindObjectOfType<LobbyViewSettingsPane>();
if (viewSettingsPane != null)
{
if (viewSettingsPane.currentTab != StringNames.OverviewCategory && viewSettingsPane.currentTab != StringNames.RolesCategory)
viewSettingsPane.ChangeTab(viewSettingsPane.currentTab);
LobbyViewPatch.ReCreateButtons(viewSettingsPane);
}
break;
case CustomRPC.AddCustomSettingsChangeMessage:
int optionId = reader.ReadInt32();
var optionItem = OptionItem.AllOptions.FirstOrDefault(opt => opt.Id == optionId);
if (optionItem == null) break;
__instance.AddCustomSettingsChangeMessage(optionItem, reader.ReadString(), reader.ReadBoolean());
break;
}
}
}
static class RPC
{
public static void AddCustomSettingsChangeMessage(this GameManager manager, OptionItem optionItem, string value, bool playSound)
{
string optionName = "";
if (optionItem.Parent?.Parent?.Parent != null)
optionName += optionItem.Parent.Parent.Parent.GetOptionNameSCM() + " → ";
if (optionItem.Parent?.Parent != null)
optionName += optionItem.Parent.Parent.GetOptionNameSCM() + " → ";
if (optionItem.Parent != null)
optionName += optionItem.Parent.GetOptionNameSCM() + " → ";
optionName += optionItem.GetOptionNameSCM();
string text = $"<font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">{optionName}</font>: <font=\"Barlow-Black SDF\" material=\"Barlow-Black Outline\">{value}</font>";
HudManager.Instance.Notifier.CustomSettingsChangeMessageLogic(optionItem, text, playSound);
}
public static void RpcSyncCustomOptions(this GameManager manager)
{
if (!AmongUsClient.Instance.AmHost) return;
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(manager.NetId, (byte)CustomRPC.SyncCustomOptions, SendOption.Reliable, -1);
writer.Write(0);
for (int i = 0; i < OptionItem.AllOptions.Count; ++i)
{
var co = OptionItem.AllOptions[i];
if (co.Id == 0 || co is TextOptionItem) continue;
if (co.Id >= 1000 && co.IsHiddenOn()) continue;
if (writer.Length > 500)
{
AmongUsClient.Instance.FinishRpcImmediately(writer);
writer = AmongUsClient.Instance.StartRpcImmediately(manager.NetId, (byte)CustomRPC.SyncCustomOptions, SendOption.Reliable, -1);
writer.Write(i);
}
writer.Write(co.CurrentValue);
}
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
public static void RpcAddCustomSettingsChangeMessage(this GameManager manager, OptionItem optionItem, string value, bool playSound)
{
manager.AddCustomSettingsChangeMessage(optionItem, value, playSound);
MessageWriter writer = AmongUsClient.Instance.StartRpcImmediately(manager.NetId, (byte)CustomRPC.AddCustomSettingsChangeMessage, SendOption.Reliable, -1);
writer.Write(optionItem.Id);
writer.Write(value);
writer.Write(playSound);
AmongUsClient.Instance.FinishRpcImmediately(writer);
}
}
}
+87 -4
View File
@@ -1,8 +1,91 @@
using System.Collections.Generic;
using System.Data;
using System.Linq;
using AmongUs.GameOptions;
using Hazel;
using UnityEngine;
using Il2CppInterop.Runtime.InteropTypes.Arrays;
using InnerNet;
using System.IO;
using System.Reflection;
using System;
using System.Security.Cryptography;
using System.Text;
using AmongUs.InnerNet.GameDataMessages;
using Object = UnityEngine.Object;
namespace HNSRevamped;
public static class Utils
{
public static bool isHideNSeek => GameOptionsManager.Instance.CurrentGameOptions.GameMode == GameModes.HideNSeek;
}
public static class Utils
{
public static bool isHideNSeek => GameOptionsManager.Instance.CurrentGameOptions.GameMode == GameModes.HideNSeek;
public static string ColorString(Color32 color, string str) => $"<#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}>{str}</color>";
public static string ColorToHex(Color32 color) => $"#{color.r:x2}{color.g:x2}{color.b:x2}{color.a:x2}";
public static string GetTabName(TabGroup tab)
{
switch (tab)
{
case TabGroup.ModSettings:
return "Mod Settings";
case TabGroup.GameModifiers:
return "Game Modifiers";
default:
return "";
}
}
public static void DestroyTranslator(this GameObject obj)
{
var translator = obj.GetComponent<TextTranslatorTMP>();
if (translator != null)
{
Object.Destroy(translator);
}
}
public static void DestroyTranslator(this MonoBehaviour obj) => obj.gameObject.DestroyTranslator();
public static void CustomSettingsChangeMessageLogic(this NotificationPopper notificationPopper, OptionItem optionItem, string text, bool playSound)
{
if (notificationPopper.lastMessageKey == 10000 + optionItem.Id && notificationPopper.activeMessages.Count > 0)
{
notificationPopper.activeMessages[notificationPopper.activeMessages.Count - 1].UpdateMessage(text);
}
else
{
notificationPopper.lastMessageKey = 10000 + optionItem.Id;
LobbyNotificationMessage settingmessage = Object.Instantiate(notificationPopper.notificationMessageOrigin, Vector3.zero, Quaternion.identity, notificationPopper.transform);
settingmessage.transform.localPosition = new Vector3(0f, 0f, -2f);
settingmessage.SetUp(text, notificationPopper.settingsChangeSprite, notificationPopper.settingsChangeColor, new Action(() =>
{
notificationPopper.OnMessageDestroy(settingmessage);
}));
notificationPopper.ShiftMessages();
notificationPopper.AddMessageToQueue(settingmessage);
}
if (playSound)
{
SoundManager.Instance.PlaySoundImmediate(notificationPopper.settingsChangeSound, false, 1f, 1f, null);
}
}
public static string GetOptionNameSCM(this OptionItem optionItem)
{
if (optionItem.Name == "Enable")
{
int id = optionItem.Id;
while (id % 10 != 0)
--id;
var optionItem2 = OptionItem.AllOptions.FirstOrDefault(opt => opt.Id == id);
return optionItem2 != null ? optionItem2.GetName() : optionItem.GetName();
}
else
return optionItem.GetName();
}
}
+536
View File
@@ -0,0 +1,536 @@
using System;
using Il2CppSystem.Collections.Generic;
using HarmonyLib;
using UnityEngine;
using Object = UnityEngine.Object;
// https://github.com/Yumenopai/TownOfHost_Y/blob/main/Patches/GameOptionsPatch.cs
namespace HNSRevamped
{
public static class ModGameOptionsMenu
{
public static int TabIndex = 0;
public static Dictionary<OptionBehaviour, int> OptionList = new();
public static Dictionary<int, OptionBehaviour> BehaviourList = new();
public static Dictionary<int, CategoryHeaderMasked> CategoryHeaderList = new();
}
[HarmonyPatch(typeof(GameOptionsMenu))]
public static class GameOptionsMenuPatch
{
[HarmonyPatch(nameof(GameOptionsMenu.Initialize)), HarmonyPrefix]
private static bool InitializePrefix(GameOptionsMenu __instance)
{
if (ModGameOptionsMenu.TabIndex < 3) return true;
if (__instance.Children == null || __instance.Children.Count == 0)
{
__instance.MapPicker.gameObject.SetActive(false);
__instance.Children = new List<OptionBehaviour>();
__instance.CreateSettings();
__instance.cachedData = GameOptionsManager.Instance.CurrentGameOptions;
for (int i = 0; i < __instance.Children.Count; i++)
{
OptionBehaviour optionBehaviour = __instance.Children[i];
optionBehaviour.OnValueChanged = new Action<OptionBehaviour>(__instance.ValueChanged);
}
__instance.InitializeControllerNavigation();
}
return false;
}
[HarmonyPatch(nameof(GameOptionsMenu.CreateSettings)), HarmonyPrefix]
private static bool CreateSettingsPrefix(GameOptionsMenu __instance)
{
if (ModGameOptionsMenu.TabIndex < 3) return true;
var modTab = (TabGroup)(ModGameOptionsMenu.TabIndex - 3);
float num = 2.0f;
const float pos_x = 0.952f;
const float pos_z = -2.0f;
for (int index = 0; index < OptionItem.AllOptions.Count; index++)
{
var option = OptionItem.AllOptions[index];
if (option.Tab != modTab) continue;
var enabled = !option.IsHiddenOn() && (option.Parent == null || (!option.Parent.IsHiddenOn() && option.Parent.GetBool()));
if (option.IsHeader || option is TextOptionItem)
{
CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
categoryHeaderMasked.SetHeader(StringNames.RolesCategory, 20);
categoryHeaderMasked.Title.text = option.GetName();
categoryHeaderMasked.transform.localScale = Vector3.one * 0.63f;
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, pos_z);
categoryHeaderMasked.transform.FindChild("HeaderText").GetComponent<TMPro.TextMeshPro>().fontStyle = TMPro.FontStyles.Bold;
categoryHeaderMasked.transform.FindChild("HeaderText").GetComponent<TMPro.TextMeshPro>().outlineWidth = 0.17f;
categoryHeaderMasked.gameObject.SetActive(enabled);
ModGameOptionsMenu.CategoryHeaderList.TryAdd(index, categoryHeaderMasked);
if (enabled) num -= 0.63f;
}
if (option is TextOptionItem) continue;
var baseGameSetting = GetSetting(option);
if (baseGameSetting == null) continue;
OptionBehaviour optionBehaviour;
switch (baseGameSetting.Type)
{
case OptionTypes.Checkbox:
{
optionBehaviour = Object.Instantiate(__instance.checkboxOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
optionBehaviour.transform.localPosition = new Vector3(pos_x, num, pos_z);
OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type);
optionBehaviour.SetClickMask(__instance.ButtonClickMask);
optionBehaviour.SetUpFromData(baseGameSetting, 20);
ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index);
break;
}
case OptionTypes.String:
{
optionBehaviour = Object.Instantiate(__instance.stringOptionOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
optionBehaviour.transform.localPosition = new Vector3(pos_x, num, pos_z);
OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type);
optionBehaviour.SetClickMask(__instance.ButtonClickMask);
optionBehaviour.SetUpFromData(baseGameSetting, 20);
ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index);
break;
}
case OptionTypes.Float:
case OptionTypes.Int:
{
optionBehaviour = Object.Instantiate(__instance.numberOptionOrigin, Vector3.zero, Quaternion.identity, __instance.settingsContainer);
optionBehaviour.transform.localPosition = new Vector3(pos_x, num, pos_z);
OptionBehaviourSetSizeAndPosition(optionBehaviour, option, baseGameSetting.Type);
optionBehaviour.SetClickMask(__instance.ButtonClickMask);
optionBehaviour.SetUpFromData(baseGameSetting, 20);
ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index);
break;
}
default:
continue;
}
optionBehaviour.transform.localPosition = new Vector3(0.952f, num, -2f);
optionBehaviour.SetClickMask(__instance.ButtonClickMask);
optionBehaviour.SetUpFromData(baseGameSetting, 20);
ModGameOptionsMenu.OptionList.TryAdd(optionBehaviour, index);
ModGameOptionsMenu.BehaviourList.TryAdd(index, optionBehaviour);
optionBehaviour.gameObject.SetActive(enabled);
__instance.Children.Add(optionBehaviour);
if (enabled) num -= 0.45f;
}
__instance.ControllerSelectable.Clear();
foreach (var x in __instance.scrollBar.GetComponentsInChildren<UiElement>())
__instance.ControllerSelectable.Add(x);
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
return false;
}
private static void OptionBehaviourSetSizeAndPosition(OptionBehaviour optionBehaviour, OptionItem option, OptionTypes type)
{
Vector3 positionOffset = new(0f, 0f, 0f);
Vector3 scaleOffset = new(0f, 0f, 0f);
Color color = new(0.7f, 0.7f, 0.7f);
float sizeDelta_x = 5.7f;
if (option.Parent?.Parent?.Parent != null)
{
scaleOffset = new(-0.18f, 0, 0);
positionOffset = new(0.3f, 0f, 0f);
color = new(0.7f, 0.5f, 0.5f);
sizeDelta_x = 5.1f;
}
else if (option.Parent?.Parent != null)
{
scaleOffset = new(-0.12f, 0, 0);
positionOffset = new(0.2f, 0f, 0f);
color = new(0.5f, 0.5f, 0.7f);
sizeDelta_x = 5.3f;
}
else if (option.Parent != null)
{
scaleOffset = new(-0.05f, 0, 0);
positionOffset = new(0.1f, 0f, 0f);
color = new(0.5f, 0.7f, 0.5f);
sizeDelta_x = 5.5f;
}
optionBehaviour.transform.FindChild("LabelBackground").GetComponent<SpriteRenderer>().color = color;
optionBehaviour.transform.FindChild("LabelBackground").localScale += new Vector3(0.9f, -0.2f, 0f) + scaleOffset;
optionBehaviour.transform.FindChild("LabelBackground").localPosition += new Vector3(-0.4f, 0f, 0f) + positionOffset;
optionBehaviour.transform.FindChild("Title Text").localPosition += new Vector3(-0.4f, 0f, 0f) + positionOffset; ;
optionBehaviour.transform.FindChild("Title Text").GetComponent<RectTransform>().sizeDelta = new Vector2(sizeDelta_x, 0.37f);
optionBehaviour.transform.FindChild("Title Text").GetComponent<TMPro.TextMeshPro>().alignment = TMPro.TextAlignmentOptions.MidlineLeft;
optionBehaviour.transform.FindChild("Title Text").GetComponent<TMPro.TextMeshPro>().fontStyle = TMPro.FontStyles.Bold;
optionBehaviour.transform.FindChild("Title Text").GetComponent<TMPro.TextMeshPro>().outlineWidth = 0.17f;
switch (type)
{
case OptionTypes.Checkbox:
optionBehaviour.transform.FindChild("Toggle").localPosition = new Vector3(1.46f, -0.042f);
break;
case OptionTypes.String:
optionBehaviour.transform.FindChild("PlusButton").localPosition += new Vector3(1.7f, 0f, 0f);
optionBehaviour.transform.FindChild("MinusButton").localPosition += new Vector3(0.9f, 0f, 0f);
optionBehaviour.transform.FindChild("Value_TMP (1)").localPosition += new Vector3(1.3f, 0f, 0f);
optionBehaviour.transform.FindChild("Value_TMP (1)").GetComponent<RectTransform>().sizeDelta = new Vector2(2.3f, 0.4f);
goto default;
case OptionTypes.Float:
case OptionTypes.Int:
optionBehaviour.transform.FindChild("PlusButton").localPosition += new Vector3(1.7f, 0f, 0f);
optionBehaviour.transform.FindChild("MinusButton").localPosition += new Vector3(0.9f, 0f, 0f);
optionBehaviour.transform.FindChild("Value_TMP").localPosition += new Vector3(1.3f, 0f, 0f);
goto default;
default:
optionBehaviour.transform.FindChild("ValueBox").localScale += new Vector3(0.2f, 0f, 0f);
optionBehaviour.transform.FindChild("ValueBox").localPosition += new Vector3(1.3f, 0f, 0f);
break;
}
}
[HarmonyPatch(nameof(GameOptionsMenu.ValueChanged)), HarmonyPrefix]
private static bool ValueChangedPrefix(GameOptionsMenu __instance, OptionBehaviour option)
{
if (ModGameOptionsMenu.TabIndex < 3) return true;
if (ModGameOptionsMenu.OptionList.TryGetValue(option, out var index))
{
var item = OptionItem.AllOptions[index];
if (item != null)
{
if (item is BooleanOptionItem)
GameManager.Instance.RpcAddCustomSettingsChangeMessage(item, item.GetBool() ? "On" : "Off", true);
else
GameManager.Instance.RpcAddCustomSettingsChangeMessage(item, item.GetString(), true);
if (item.Id == 0)
{
ReOpenSettingMenu();
var viewSettingsPane = Object.FindObjectOfType<LobbyViewSettingsPane>();
if (viewSettingsPane != null)
LobbyViewPatch.ReCreateButtons(viewSettingsPane);
}
else if (item.Id == 2)
{
ReOpenSettingMenu();
var viewSettingsPane = Object.FindObjectOfType<LobbyViewSettingsPane>();
if (viewSettingsPane != null)
LobbyViewPatch.ReCreateButtons(viewSettingsPane);
}
else if (item.Children.Count > 0)
ReCreateSettings(__instance);
}
}
return false;
}
private static void ReCreateSettings(GameOptionsMenu __instance)
{
if (ModGameOptionsMenu.TabIndex < 3) return;
var modTab = (TabGroup)(ModGameOptionsMenu.TabIndex - 3);
float num = 2.0f;
for (int index = 0; index < OptionItem.AllOptions.Count; index++)
{
var option = OptionItem.AllOptions[index];
if (option.Tab != modTab) continue;
var enabled = !option.IsHiddenOn() && (option.Parent == null || (!option.Parent.IsHiddenOn() && option.Parent.GetBool()));
if (ModGameOptionsMenu.CategoryHeaderList.TryGetValue(index, out var categoryHeaderMasked))
{
categoryHeaderMasked.transform.localPosition = new Vector3(-0.903f, num, -2f);
categoryHeaderMasked.gameObject.SetActive(enabled);
if (enabled) num -= 0.63f;
}
if (ModGameOptionsMenu.BehaviourList.TryGetValue(index, out var optionBehaviour))
{
optionBehaviour.transform.localPosition = new Vector3(0.952f, num, -2f);
optionBehaviour.gameObject.SetActive(enabled);
if (enabled) num -= 0.45f;
}
}
__instance.ControllerSelectable.Clear();
foreach (var x in __instance.scrollBar.GetComponentsInChildren<UiElement>())
__instance.ControllerSelectable.Add(x);
__instance.scrollBar.SetYBoundsMax(-num - 1.65f);
}
public static BaseGameSetting GetSetting(OptionItem item)
{
BaseGameSetting baseGameSetting = null;
if (item is BooleanOptionItem)
{
baseGameSetting = new CheckboxGameSetting
{
Type = OptionTypes.Checkbox,
};
}
else if (item is IntegerOptionItem)
{
IntegerOptionItem intItem = item as IntegerOptionItem;
baseGameSetting = new IntGameSetting
{
Type = OptionTypes.Int,
Value = intItem.GetInt(),
Increment = intItem.Rule.Step,
ValidRange = new IntRange(intItem.Rule.MinValue, intItem.Rule.MaxValue),
ZeroIsInfinity = false,
SuffixType = NumberSuffixes.Multiplier,
FormatString = string.Empty,
};
}
else if (item is FloatOptionItem)
{
FloatOptionItem floatItem = item as FloatOptionItem;
baseGameSetting = new FloatGameSetting
{
Type = OptionTypes.Float,
Value = floatItem.GetFloat(),
Increment = floatItem.Rule.Step,
ValidRange = new FloatRange(floatItem.Rule.MinValue, floatItem.Rule.MaxValue),
ZeroIsInfinity = false,
SuffixType = NumberSuffixes.Multiplier,
FormatString = string.Empty,
};
}
else if (item is StringOptionItem)
{
StringOptionItem stringItem = item as StringOptionItem;
baseGameSetting = new StringGameSetting
{
Type = OptionTypes.String,
Values = new StringNames[stringItem.Selections.Length],
Index = stringItem.GetInt(),
};
}
if (baseGameSetting != null)
baseGameSetting.Title = StringNames.Accept;
return baseGameSetting;
}
public static void ReOpenSettingMenu()
{
var tab = ModGameOptionsMenu.TabIndex;
if (GameSettingMenu.Instance == null) return;
GameSettingMenu.Instance.Close();
OptionsConsole optionsConsole = null;
foreach (var console in Object.FindObjectsOfType<OptionsConsole>())
{
if (console.HostOnly)
optionsConsole = console;
}
if (optionsConsole == null) return;
GameObject gameObject = Object.Instantiate(optionsConsole.MenuPrefab);
gameObject.transform.SetParent(Camera.main.transform, false);
gameObject.transform.localPosition = optionsConsole.CustomPosition;
new LateTask(() => {
if (GameSettingMenu.Instance == null) return;
GameSettingMenu.Instance.ChangeTab(tab, false);
}, 0f);
}
}
[HarmonyPatch(typeof(ToggleOption))]
public static class ToggleOptionPatch
{
[HarmonyPatch(nameof(ToggleOption.Initialize)), HarmonyPrefix]
private static bool InitializePrefix(ToggleOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
__instance.TitleText.text = item.GetName();
__instance.CheckMark.enabled = item.GetBool();
return false;
}
return true;
}
[HarmonyPatch(nameof(ToggleOption.Toggle)), HarmonyPrefix]
private static bool TogglePrefix(ToggleOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
__instance.CheckMark.enabled = !__instance.CheckMark.enabled;
var item = OptionItem.AllOptions[index];
item.SetValue(__instance.GetBool() ? 1 : 0);
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
}
[HarmonyPatch(typeof(NumberOption))]
public static class NumberOptionPatch
{
[HarmonyPatch(nameof(NumberOption.Initialize)), HarmonyPrefix]
private static bool InitializePrefix(NumberOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
__instance.TitleText.text = item.GetName();
return false;
}
return true;
}
[HarmonyPatch(nameof(NumberOption.UpdateValue)), HarmonyPrefix]
private static bool UpdateValuePrefix(NumberOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
if (item is IntegerOptionItem integerOptionItem)
{
integerOptionItem.SetValue(integerOptionItem.Rule.GetNearestIndex(__instance.GetInt()));
}
else if (item is FloatOptionItem floatOptionItem)
{
floatOptionItem.SetValue(floatOptionItem.Rule.GetNearestIndex(__instance.GetFloat()));
}
return false;
}
return true;
}
[HarmonyPatch(nameof(NumberOption.FixedUpdate)), HarmonyPrefix]
private static bool FixedUpdatePrefix(NumberOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
if (__instance.oldValue != __instance.Value)
{
__instance.oldValue = __instance.Value;
__instance.ValueText.text = item.GetString();
}
return false;
}
return true;
}
[HarmonyPatch(nameof(NumberOption.Increase)), HarmonyPrefix]
public static bool IncreasePrefix(NumberOption __instance)
{
if (__instance.Value == __instance.ValidRange.max)
{
__instance.Value = __instance.ValidRange.min;
__instance.UpdateValue();
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
[HarmonyPatch(nameof(NumberOption.Decrease)), HarmonyPrefix]
public static bool DecreasePrefix(NumberOption __instance)
{
if (__instance.Value == __instance.ValidRange.min)
{
__instance.Value = __instance.ValidRange.max;
__instance.UpdateValue();
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
[HarmonyPatch(nameof(NumberOption.AdjustButtonsActiveState)), HarmonyPrefix]
public static bool AdjustButtonsActiveStatePrefix(NumberOption __instance)
{
__instance.MinusBtn.SetInteractable(true);
__instance.PlusBtn.SetInteractable(true);
return false;
}
}
[HarmonyPatch(typeof(StringOption))]
public static class StringOptionPatch
{
[HarmonyPatch(nameof(StringOption.Initialize)), HarmonyPrefix]
private static bool InitializePrefix(StringOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
__instance.TitleText.text = item.GetName();
return false;
}
return true;
}
[HarmonyPatch(nameof(StringOption.UpdateValue)), HarmonyPrefix]
private static bool UpdateValuePrefix(StringOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
item.SetValue(__instance.GetInt());
return false;
}
return true;
}
[HarmonyPatch(nameof(StringOption.FixedUpdate)), HarmonyPrefix]
private static bool FixedUpdatePrefix(StringOption __instance)
{
if (ModGameOptionsMenu.OptionList.TryGetValue(__instance, out var index))
{
var item = OptionItem.AllOptions[index];
if (__instance.oldValue != __instance.Value)
{
__instance.oldValue = __instance.Value;
__instance.ValueText.text = item.GetString();
}
return false;
}
return true;
}
[HarmonyPatch(nameof(StringOption.Increase)), HarmonyPrefix]
public static bool IncreasePrefix(StringOption __instance)
{
if (__instance.Value == __instance.Values.Length - 1)
{
__instance.Value = 0;
__instance.UpdateValue();
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
[HarmonyPatch(nameof(StringOption.Decrease)), HarmonyPrefix]
public static bool DecreasePrefix(StringOption __instance)
{
if (__instance.Value == 0)
{
__instance.Value = __instance.Values.Length - 1;
__instance.UpdateValue();
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
[HarmonyPatch(nameof(StringOption.AdjustButtonsActiveState)), HarmonyPrefix]
public static bool AdjustButtonsActiveStatePrefix(StringOption __instance)
{
__instance.MinusBtn.SetInteractable(true);
__instance.PlusBtn.SetInteractable(true);
return false;
}
}
[HarmonyPatch(typeof(PlayerOption))]
public static class PlayerOptionPatch
{
[HarmonyPatch(nameof(PlayerOption.Increase)), HarmonyPrefix]
public static bool IncreasePrefix(PlayerOption __instance)
{
if (__instance.Value == __instance.Values.Count - 1)
{
__instance.Value = -1;
__instance.UpdateValue();
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
[HarmonyPatch(nameof(PlayerOption.Decrease)), HarmonyPrefix]
public static bool DecreasePrefix(PlayerOption __instance)
{
if (__instance.Value < 0)
{
__instance.Value = __instance.Values.Count - 1;
__instance.UpdateValue();
__instance.OnValueChanged.Invoke(__instance);
return false;
}
return true;
}
[HarmonyPatch(nameof(PlayerOption.AdjustButtonsActiveState)), HarmonyPrefix]
public static bool AdjustButtonsActiveStatePrefix(PlayerOption __instance)
{
__instance.MinusBtn.SetInteractable(true);
__instance.PlusBtn.SetInteractable(true);
return false;
}
}
}
+183
View File
@@ -0,0 +1,183 @@
using System;
using HarmonyLib;
using System.Collections.Generic;
using TMPro;
using UnityEngine;
using Object = UnityEngine.Object;
// https://github.com/Yumenopai/TownOfHost_Y/blob/main/Patches/GameSettingMenuPatch.cs
namespace HNSRevamped
{
[HarmonyPatch(typeof(GameSettingMenu))]
public class GameSettingMenuPatch
{
private static GameOptionsMenu templateGameOptionsMenu;
private static PassiveButton templateGameSettingsButton;
static Dictionary<TabGroup, PassiveButton> ModSettingsButtons = new();
static Dictionary<TabGroup, GameOptionsMenu> ModSettingsTabs = new();
[HarmonyPatch(nameof(GameSettingMenu.Start)), HarmonyPrefix]
[HarmonyPriority(Priority.First)]
public static void StartPostfix(GameSettingMenu __instance)
{
ModSettingsButtons = new();
int tabNum = 0;
foreach (var tab in Enum.GetValues<TabGroup>())
{
var button = Object.Instantiate(templateGameSettingsButton, __instance.GameSettingsButton.transform.parent);
button.gameObject.SetActive(true);
button.name = "Button_" + Utils.GetTabName(tab);
var label = button.GetComponentInChildren<TextMeshPro>();
label.DestroyTranslator();
label.text = Utils.GetTabName(tab);
Vector3 offset = new (0.0f, 0.5f * (tabNum / 2), 0.0f);
button.transform.localPosition = ((tabNum % 2 == 0) ? new Vector3(-3.9f, -0.9f, 0f) : new Vector3(-2.4f, -0.9f, 0f)) - offset;
button.transform.localScale = new(0.45f, 0.6f, 1f);
var buttonComponent = button.GetComponent<PassiveButton>();
buttonComponent.OnClick = new();
buttonComponent.OnClick.AddListener((Action)(() => __instance.ChangeTab((int)tab + 3, false)));
ModSettingsButtons.Add(tab, button);
++tabNum;
}
ModGameOptionsMenu.OptionList = new();
ModGameOptionsMenu.BehaviourList = new();
ModGameOptionsMenu.CategoryHeaderList = new();
ModSettingsTabs = new();
foreach (var tab in Enum.GetValues<TabGroup>())
{
var setTab = Object.Instantiate(templateGameOptionsMenu, __instance.GameSettingsTab.transform.parent);
setTab.name = Utils.GetTabName(tab);
setTab.gameObject.SetActive(false);
ModSettingsTabs.Add(tab, setTab);
}
foreach (var tab in Enum.GetValues<TabGroup>())
{
if (ModSettingsButtons.TryGetValue(tab, out var button))
__instance.ControllerSelectable.Add(button);
}
}
private static void SetDefaultButton(GameSettingMenu __instance)
{
var gamepreset = __instance.GamePresetsButton;
gamepreset.gameObject.SetActive(false);
var gamesettings = __instance.GameSettingsButton;
gamesettings.transform.localScale = new Vector3(0.45f, 0.6f, 1f);
gamesettings.transform.localPosition = new Vector3(-3.9f, -0.4f, 0f);
var rolesettings = __instance.RoleSettingsButton;
rolesettings.transform.localScale = new Vector3(0.45f, 0.6f, 1f);
rolesettings.transform.localPosition = new(-2.4f, -0.4f, 0f);
var label = rolesettings.GetComponentInChildren<TextMeshPro>();
label.DestroyTranslator();
label.text = "Vanilla Roles";
}
[HarmonyPatch(nameof(GameSettingMenu.ChangeTab)), HarmonyPrefix]
public static bool ChangeTabPrefix(GameSettingMenu __instance, ref int tabNum, [HarmonyArgument(1)] bool previewOnly)
{
if (!previewOnly)
ModGameOptionsMenu.TabIndex = tabNum;
GameOptionsMenu settingsTab;
PassiveButton button;
if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly)
{
foreach (var tab in Enum.GetValues<TabGroup>())
{
if (ModSettingsTabs.TryGetValue(tab, out settingsTab) && settingsTab != null)
settingsTab.gameObject.SetActive(false);
}
foreach (var tab in Enum.GetValues<TabGroup>())
{
if (ModSettingsButtons.TryGetValue(tab, out button) && button != null)
button.SelectButton(false);
}
}
if (tabNum < 3) return true;
if ((previewOnly && Controller.currentTouchType == Controller.TouchType.Joystick) || !previewOnly)
{
__instance.PresetsTab.gameObject.SetActive(false);
__instance.GameSettingsTab.gameObject.SetActive(false);
__instance.RoleSettingsTab.gameObject.SetActive(false);
__instance.GamePresetsButton.SelectButton(false);
__instance.GameSettingsButton.SelectButton(false);
__instance.RoleSettingsButton.SelectButton(false);
if (ModSettingsTabs.TryGetValue((TabGroup)(tabNum - 3), out settingsTab) && settingsTab != null)
{
settingsTab.gameObject.SetActive(true);
__instance.MenuDescriptionText.DestroyTranslator();
switch ((TabGroup)(tabNum - 3))
{
case TabGroup.ModSettings:
__instance.MenuDescriptionText.text = "Mod Settings";
break;
case TabGroup.GameModifiers:
__instance.MenuDescriptionText.text = "Game Modifiers";
break;
}
}
}
if (previewOnly)
{
__instance.ToggleLeftSideDarkener(false);
__instance.ToggleRightSideDarkener(true);
return false;
}
__instance.ToggleLeftSideDarkener(true);
__instance.ToggleRightSideDarkener(false);
if (ModSettingsButtons.TryGetValue((TabGroup)(tabNum - 3), out button) && button != null)
button.SelectButton(true);
return false;
}
[HarmonyPatch(nameof(GameSettingMenu.OnEnable)), HarmonyPrefix]
private static bool OnEnablePrefix(GameSettingMenu __instance)
{
if (templateGameOptionsMenu == null)
{
templateGameOptionsMenu = Object.Instantiate(__instance.GameSettingsTab, __instance.GameSettingsTab.transform.parent);
templateGameOptionsMenu.gameObject.SetActive(false);
}
if (templateGameSettingsButton == null)
{
templateGameSettingsButton = Object.Instantiate(__instance.GameSettingsButton, __instance.GameSettingsButton.transform.parent);
templateGameSettingsButton.gameObject.SetActive(false);
}
SetDefaultButton(__instance);
ControllerManager.Instance.OpenOverlayMenu(__instance.name, __instance.BackButton, __instance.DefaultButtonSelected, __instance.ControllerSelectable, false);
DestroyableSingleton<HudManager>.Instance.menuNavigationPrompts.SetActive(false);
if (Controller.currentTouchType != Controller.TouchType.Joystick)
__instance.ChangeTab(1, Controller.currentTouchType == Controller.TouchType.Joystick);
__instance.StartCoroutine(__instance.CoSelectDefault());
return false;
}
[HarmonyPatch(nameof(GameSettingMenu.Close)), HarmonyPostfix]
private static void ClosePostfix(GameSettingMenu __instance)
{
foreach (var button in ModSettingsButtons.Values)
Object.Destroy(button);
foreach (var tab in ModSettingsTabs.Values)
Object.Destroy(tab);
ModSettingsButtons = new();
ModSettingsTabs = new();
}
}
[HarmonyPatch(typeof(PlayerControl), nameof(PlayerControl.RpcSyncSettings))]
public class RpcSyncSettingsPatch
{
public static void Postfix()
{
OptionItem.SyncAllOptions();
}
}
}
+162
View File
@@ -0,0 +1,162 @@
using System;
using TMPro;
using HarmonyLib;
using UnityEngine;
using System.Collections.Generic;
using Object = UnityEngine.Object;
namespace HNSRevamped
{
[HarmonyPatch(typeof(LobbyViewSettingsPane))]
public class LobbyViewPatch
{
private static Dictionary<TabGroup, GameObject> ViewSettingsButtons = new();
private static float num;
[HarmonyPatch(nameof(LobbyViewSettingsPane.Awake))]
[HarmonyPostfix]
public static void AwakePostfix(LobbyViewSettingsPane __instance)
{
__instance.gameModeText.DestroyTranslator();
var OverviewButton = __instance.taskTabButton.gameObject;
OverviewButton.transform.localScale = new Vector3(0.4f, 1f, 1f);
OverviewButton.transform.localPosition += new Vector3(-1.1f, 0f, 0f);
var RolesButton = __instance.rolesTabButton.gameObject;
RolesButton.transform.localScale = new Vector3(0.4f, 1f, 1f);
num = 1.4f;
RolesButton.transform.localPosition = OverviewButton.transform.localPosition + Vector3.right * num * 1f;
var label = RolesButton.transform.Find("FontPlacer").GetComponentInChildren<TextMeshPro>();
label.DestroyTranslator();
label.text = "Vanilla Roles";
ViewSettingsButtons = new();
num = 2.8f;
}
[HarmonyPatch(nameof(LobbyViewSettingsPane.ChangeTab))]
[HarmonyPrefix]
public static bool ChangeTabPrefix(LobbyViewSettingsPane __instance, [HarmonyArgument(0)] StringNames category)
{
if (category == StringNames.OverviewCategory || category == StringNames.RolesCategory)
{
foreach (var button in ViewSettingsButtons.Values)
button.GetComponent<PassiveButton>().SelectButton(false);
return true;
}
__instance.currentTab = category;
for (int i = 0; i < __instance.settingsInfo.Count; i++)
{
Object.Destroy(__instance.settingsInfo[i].gameObject);
}
__instance.settingsInfo.Clear();
__instance.taskTabButton.SelectButton(false);
__instance.rolesTabButton.SelectButton(false);
foreach (var tab in ViewSettingsButtons.Keys)
{
if (tab == (TabGroup)__instance.currentTab)
ViewSettingsButtons[tab].GetComponent<PassiveButton>().SelectButton(true);
else
ViewSettingsButtons[tab].GetComponent<PassiveButton>().SelectButton(false);
}
DrawOptions(__instance, (TabGroup)__instance.currentTab);
__instance.scrollBar.ScrollToTop();
return false;
}
public static void DrawOptions(LobbyViewSettingsPane __instance, TabGroup tab)
{
float num = 1.44f;
int settingsCount = 0;
foreach (var option in OptionItem.AllOptions)
{
if (option.IsHiddenOn() || option.Tab != tab || (option.Parent != null && (option.Parent.IsHiddenOn() || !option.Parent.GetBool()))) continue;
if (option.IsHeader || option is TextOptionItem)
{
num -= 0.85f;
CategoryHeaderMasked categoryHeaderMasked = Object.Instantiate(__instance.categoryHeaderOrigin);
categoryHeaderMasked.SetHeader(StringNames.None, 61);
categoryHeaderMasked.transform.SetParent(__instance.settingsContainer);
categoryHeaderMasked.transform.localScale = Vector3.one;
categoryHeaderMasked.transform.localPosition = new Vector3(-9.77f, num, -2f);
__instance.settingsInfo.Add(categoryHeaderMasked.gameObject);
num -= 1f;
categoryHeaderMasked.DestroyTranslator();
categoryHeaderMasked.Title.text = option.GetName();
settingsCount = 0;
}
if (option is TextOptionItem) continue;
ViewSettingsInfoPanel viewSettingsInfoPanel = Object.Instantiate(__instance.infoPanelOrigin);
viewSettingsInfoPanel.transform.SetParent(__instance.settingsContainer);
viewSettingsInfoPanel.transform.localScale = Vector3.one;
float num2;
if (settingsCount % 2 == 0)
{
num2 = -8.95f;
if (settingsCount > 0)
{
num -= 0.85f;
}
}
else
{
num2 = -3f;
}
viewSettingsInfoPanel.transform.localPosition = new Vector3(num2, num, -2f);
if (option is BooleanOptionItem)
{
viewSettingsInfoPanel.SetInfoCheckbox(StringNames.None, 61, option.GetBool());
}
else
{
viewSettingsInfoPanel.SetInfo(StringNames.None, option.GetString(), 61);
}
viewSettingsInfoPanel.titleText.text = option.GetName();
if (option.Parent?.Parent?.Parent != null)
{
viewSettingsInfoPanel.transform.FindChild("LabelBackground").GetComponent<SpriteRenderer>().color = new(0.7f, 0.5f, 0.5f);
}
else if (option.Parent?.Parent != null)
{
viewSettingsInfoPanel.transform.FindChild("LabelBackground").GetComponent<SpriteRenderer>().color = new(0.5f, 0.5f, 0.7f);
}
else if (option.Parent != null)
{
viewSettingsInfoPanel.transform.FindChild("LabelBackground").GetComponent<SpriteRenderer>().color = new(0.5f, 0.7f, 0.5f);
}
__instance.settingsInfo.Add(viewSettingsInfoPanel.gameObject);
++settingsCount;
}
__instance.scrollBar.SetYBoundsMax(-num - 1.44f);
}
public static void ReCreateButtons(LobbyViewSettingsPane __instance)
{
foreach (var button in ViewSettingsButtons.Values)
Object.Destroy(button.gameObject);
ViewSettingsButtons = new();
var OverviewButton = __instance.taskTabButton.gameObject;
var RolesButton = __instance.rolesTabButton.gameObject;
OverviewButton.transform.localScale = new Vector3(0.4f, 1f, 1f);
RolesButton.transform.localScale = new Vector3(0.4f, 1f, 1f);
num = 1.4f;
RolesButton.transform.localPosition = OverviewButton.transform.localPosition + Vector3.right * num * 1f;
num = 2.8f;
}
[HarmonyPatch(nameof(LobbyViewSettingsPane.OnEnable))]
[HarmonyPostfix]
public static void OnEnablePostfix(LobbyViewSettingsPane __instance)
{
ReCreateButtons(__instance);
}
}
}