using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using BepInEx; using HarmonyLib; using Mirror; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: AssemblyVersion("0.0.0.0")] [BepInPlugin("com.buhe.gwyfmachinecontrolbuhe", "GWYF-MachineControl-BUHE", "0.2.1")] public sealed class GwyfMachineControlBuhePlugin : BaseUnityPlugin { private const string DefaultSettingsText = "# GWYF-MachineControl-BUHE\n# 把这个文件放在 GWYF-MachineControl-BUHE.dll 同目录。\n# Enabled: true 开启,false 关闭。\n# PositionsPerFloor: 每一层、每一种机器固定刷几个机位。\n# Machines: 要固定刷的机器英文参数,多个机器用英文逗号隔开。\n# BannedMachines: 禁止随机刷出的机器英文参数,多个机器用英文逗号隔开;留空就是不禁用。\n# 例子:Machines=Crash,DuckRace 且 PositionsPerFloor=2,表示每层 2 个 Crash 机位 + 2 个 DuckRace 机位。\n\nEnabled=true\nPositionsPerFloor=2\nMachines=Crash\nBannedMachines=\n"; private const string MachineDocText = "GWYF-MachineControl-BUHE 机位参数说明\n\n这个文件只是说明书,不会影响模组效果。\n真正要改的是同目录的:\n GWYF-MachineControl-BUHE.settings.txt\n\n配置文件示例:\n Enabled=true\n PositionsPerFloor=2\n Machines=Crash,DuckRace\n\n参数说明:\n Enabled=true\n 开启模组。\n\n Enabled=false\n 关闭模组。\n\n PositionsPerFloor=2\n 每一层、每一种机器固定刷 2 个机位。\n 注意:有些机位本身会显示两台机器,所以 2 个机位看起来可能是 4 台机器。\n\n Machines=Crash\n 每一层固定刷 Crash。\n\n Machines=Crash,DuckRace\n 每一层固定刷 2 个 Crash 机位,再固定刷 2 个 DuckRace 机位。\n 如果 PositionsPerFloor=1,就变成每层 1 个 Crash + 1 个 DuckRace。\n\n BannedMachines=\n 不禁用任何机器。\n\n BannedMachines=DuckRace\n 禁止随机刷出 DuckRace。\n 如果游戏原本随机到了 DuckRace,模组会把它替换成 Machines 里面第一个可用、并且没有被禁用的机器。\n 例如 Machines=Crash 且 BannedMachines=DuckRace,就会把随机到的鸭子赛跑替换成崩溃。\n\n BannedMachines=DuckRace,Slots\n 禁止随机刷出 DuckRace 和 Slots。\n\n常用机器参数:\n Crash = 崩溃\n DuckRace = 鸭子赛跑\n Slots = 老虎/老虎机\n Roulette = 轮盘\n Blackjack = 二十一点\n Poker = 扑克\n Baccarat = 百家乐\n Craps = 骰子\n CoinFlip = 抛硬币\n DragonTower = 龙塔\n HiLoGame = 高低牌\n Keno = 基诺\n MoneyWheel = 金钱轮盘\n Plinko = 弹珠机\n ClawMachine = 抓娃娃机\n WheelOfFortune = 幸运转盘\n BeybladeBattle = 陀螺对战(MoreGames 模组)\n\n填写规则:\n 1. Machines 后面填英文参数,不要填中文名。\n 正确:Machines=Crash,DuckRace\n 错误:Machines=崩溃,鸭子赛跑\n\n 2. 多个机器之间用英文逗号隔开。\n 正确:Crash,DuckRace,Slots\n 错误:Crash,DuckRace,Slots\n\n 3. 改完 GWYF-MachineControl-BUHE.settings.txt 后,重新进一把赌场才会生效。\n\n 4. BannedMachines 也填英文参数,不要填中文名。\n 正确:BannedMachines=DuckRace,Slots\n 错误:BannedMachines=鸭子赛跑,老虎机\n\n 5. 如果某个机器参数当前游戏或当前模组环境里找不到,模组会跳过它,并在 BepInEx 日志里提示。\n"; internal static GwyfMachineControlBuhePlugin Instance; internal static ExternalSettings Settings = ExternalSettings.Default; internal static string PluginFolder; private Harmony _harmony; private void Awake() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown Instance = this; PluginFolder = Path.GetDirectoryName(typeof(GwyfMachineControlBuhePlugin).Assembly.Location); EnsureExternalFiles(); Settings = LoadExternalSettings(); try { _harmony = new Harmony("com.buhe.gwyfmachinecontrolbuhe"); _harmony.PatchAll(typeof(GwyfMachineControlBuhePlugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)("GWYF-MachineControl-BUHE loaded. PositionsPerFloor=" + Settings.PositionsPerFloor + ", Machines=" + string.Join(",", Settings.Machines.ToArray()) + ", BannedMachines=" + string.Join(",", Settings.BannedMachines.ToArray()))); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Failed to install Harmony patches: " + ex)); _harmony = null; } } private void OnDestroy() { if (_harmony != null) { try { _harmony.UnpatchSelf(); } catch { } _harmony = null; } } internal void LogInfo(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } internal void LogWarning(string message) { ((BaseUnityPlugin)this).Logger.LogWarning((object)message); } internal static ExternalSettings LoadExternalSettings() { string settingsPath = GetSettingsPath(); ExternalSettings externalSettings = ExternalSettings.Default; try { if (!File.Exists(settingsPath)) { return externalSettings; } string[] array = File.ReadAllLines(settingsPath); for (int i = 0; i < array.Length; i++) { string text = StripComment(array[i]).Trim(); if (text.Length == 0) { continue; } int num = text.IndexOf('='); if (num <= 0) { continue; } string text2 = text.Substring(0, num).Trim(); string text3 = text.Substring(num + 1).Trim(); if (text2.Equals("Enabled", StringComparison.OrdinalIgnoreCase)) { if (bool.TryParse(text3, out var result)) { externalSettings.Enabled = result; } } else if (text2.Equals("PositionsPerFloor", StringComparison.OrdinalIgnoreCase)) { if (int.TryParse(text3, out var result2)) { externalSettings.PositionsPerFloor = Math.Max(0, result2); } } else if (text2.Equals("Machines", StringComparison.OrdinalIgnoreCase)) { externalSettings.Machines = ParseMachineList(text3); } else if (text2.Equals("BannedMachines", StringComparison.OrdinalIgnoreCase)) { externalSettings.BannedMachines = ParseMachineList(text3); } } } catch (Exception ex) { if ((Object)(object)Instance != (Object)null) { Instance.LogWarning("Failed to load external settings, using defaults: " + ex.Message); } } if (externalSettings.Machines.Count == 0) { externalSettings.Machines.Add("Crash"); } return externalSettings; } private static string StripComment(string line) { if (line == null) { return ""; } int num = line.IndexOf('#'); int num2 = line.IndexOf("//", StringComparison.Ordinal); int num3 = -1; if (num >= 0) { num3 = num; } if (num2 >= 0 && (num3 < 0 || num2 < num3)) { num3 = num2; } if (num3 < 0) { return line; } return line.Substring(0, num3); } private static List ParseMachineList(string value) { List list = new List(); if (string.IsNullOrEmpty(value)) { return list; } string[] array = value.Split(new char[1] { ',' }); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.Length != 0 && !ContainsIgnoreCase(list, text)) { list.Add(text); } } return list; } private static bool ContainsIgnoreCase(List values, string value) { for (int i = 0; i < values.Count; i++) { if (values[i].Equals(value, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static void EnsureExternalFiles() { try { string settingsPath = GetSettingsPath(); if (!File.Exists(settingsPath)) { File.WriteAllText(settingsPath, "# GWYF-MachineControl-BUHE\n# 把这个文件放在 GWYF-MachineControl-BUHE.dll 同目录。\n# Enabled: true 开启,false 关闭。\n# PositionsPerFloor: 每一层、每一种机器固定刷几个机位。\n# Machines: 要固定刷的机器英文参数,多个机器用英文逗号隔开。\n# BannedMachines: 禁止随机刷出的机器英文参数,多个机器用英文逗号隔开;留空就是不禁用。\n# 例子:Machines=Crash,DuckRace 且 PositionsPerFloor=2,表示每层 2 个 Crash 机位 + 2 个 DuckRace 机位。\n\nEnabled=true\nPositionsPerFloor=2\nMachines=Crash\nBannedMachines=\n"); } } catch (Exception ex) { if ((Object)(object)Instance != (Object)null) { Instance.LogWarning("Failed to create external settings file: " + ex.Message); } } } internal static string GetSettingsPath() { return Path.Combine(PluginFolder ?? "", "GWYF-MachineControl-BUHE.settings.txt"); } } internal sealed class ExternalSettings { internal bool Enabled = true; internal int PositionsPerFloor = 2; internal List Machines = new List { "Crash" }; internal List BannedMachines = new List(); internal static ExternalSettings Default => new ExternalSettings(); } [HarmonyPatch(typeof(StampManager), "PreAssignGamesToStamps")] internal static class MachineControlPreAssignmentPatch { private static readonly FieldInfo PreAssignedGamesField = AccessTools.Field(typeof(StampManager), "_preAssignedGames"); private static readonly FieldInfo AllStampsField = AccessTools.Field(typeof(StampManager), "allStamps"); private static readonly FieldInfo FloorLootTablesField = AccessTools.Field(typeof(StampManager), "floorLootTables"); private static readonly MethodInfo GetPositionKeyMethod = AccessTools.Method(typeof(StampManager), "GetPositionKey", (Type[])null, (Type[])null); [HarmonyPriority(0)] private static void Postfix(StampManager __instance) { if ((Object)(object)__instance == (Object)null || !NetworkServer.active) { return; } ExternalSettings externalSettings = (GwyfMachineControlBuhePlugin.Settings = GwyfMachineControlBuhePlugin.LoadExternalSettings()); if (!externalSettings.Enabled) { return; } int num = Math.Max(0, externalSettings.PositionsPerFloor); if (num <= 0 || externalSettings.Machines.Count == 0) { return; } try { Dictionary dictionary = PreAssignedGamesField.GetValue(__instance) as Dictionary; IList list = AllStampsField.GetValue(__instance) as IList; if (dictionary == null || list == null || dictionary.Count == 0 || list.Count == 0) { Log("Stamp assignment data is not ready."); return; } Dictionary dictionary2 = FindConfiguredPrefabs(__instance, dictionary, externalSettings.Machines); if (dictionary2.Count == 0) { Log("No configured machine prefabs were found. Requested: " + string.Join(",", externalSettings.Machines.ToArray()) + "."); return; } List sortedStamps = CollectSortedStamps(list); Dictionary totalByFloor = new Dictionary(); Dictionary> dictionary3 = GroupStampsByFloor(sortedStamps, totalByFloor); Dictionary dictionary4 = new Dictionary(); HashSet hashSet = new HashSet(); List list2 = new List(dictionary3.Keys); list2.Sort(); for (int i = 0; i < list2.Count; i++) { int num2 = list2[i]; List list3 = dictionary3[num2]; int num3 = 0; for (int j = 0; j < externalSettings.Machines.Count; j++) { string text = externalSettings.Machines[j]; if (!dictionary2.TryGetValue(text, out var value)) { continue; } for (int k = 0; k < num; k++) { if (num3 >= list3.Count) { break; } ObjectStamp stamp = list3[num3++]; string positionKey = GetPositionKey(__instance, stamp); if (!string.IsNullOrEmpty(positionKey) && dictionary.ContainsKey(positionKey)) { dictionary[positionKey] = value; hashSet.Add(positionKey); Increment(dictionary4, MakeFloorMachineKey(num2, text)); } } } } int bannedReplaced = ReplaceBannedMachines(__instance, sortedStamps, dictionary, hashSet, dictionary2, externalSettings); LogFloorSummary(totalByFloor, dictionary4, externalSettings.Machines, num, bannedReplaced); } catch (Exception ex) { Log("Machine assignment patch failed: " + ex.GetType().Name + ": " + ex.Message); } } private static List CollectSortedStamps(IList stamps) { List list = new List(); for (int i = 0; i < stamps.Count; i++) { object? obj = stamps[i]; ObjectStamp val = (ObjectStamp)((obj is ObjectStamp) ? obj : null); if ((Object)(object)val != (Object)null && (Object)(object)val.Floor != (Object)null) { list.Add(val); } } list.Sort(delegate(ObjectStamp a, ObjectStamp b) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) int num = a.Floor.floorIndex.CompareTo(b.Floor.floorIndex); if (num != 0) { return num; } Vector3 position = ((Component)a).transform.position; Vector3 position2 = ((Component)b).transform.position; int num2 = position.x.CompareTo(position2.x); return (num2 != 0) ? num2 : position.z.CompareTo(position2.z); }); return list; } private static Dictionary> GroupStampsByFloor(List sortedStamps, Dictionary totalByFloor) { Dictionary> dictionary = new Dictionary>(); for (int i = 0; i < sortedStamps.Count; i++) { ObjectStamp val = sortedStamps[i]; if (!((Object)(object)val == (Object)null) && !((Object)(object)val.Floor == (Object)null)) { int floorIndex = val.Floor.floorIndex; Increment(totalByFloor, floorIndex); if (!dictionary.TryGetValue(floorIndex, out var value)) { value = (dictionary[floorIndex] = new List()); } value.Add(val); } } return dictionary; } private static int ReplaceBannedMachines(StampManager manager, List sortedStamps, Dictionary assigned, HashSet forcedKeys, Dictionary prefabByMachine, ExternalSettings settings) { if (settings.BannedMachines == null || settings.BannedMachines.Count == 0) { return 0; } GameObject val = FindBanReplacement(prefabByMachine, settings); if ((Object)(object)val == (Object)null) { Log("BannedMachines is set, but no replacement machine was available. Banned machines will not be replaced."); return 0; } int num = 0; for (int i = 0; i < sortedStamps.Count; i++) { ObjectStamp stamp = sortedStamps[i]; string positionKey = GetPositionKey(manager, stamp); if (!string.IsNullOrEmpty(positionKey) && !forcedKeys.Contains(positionKey) && assigned.ContainsKey(positionKey)) { GameObject prefab = assigned[positionKey]; if (IsBannedPrefab(prefab, settings.BannedMachines)) { assigned[positionKey] = val; num++; } } } Log("Banned machine replacement complete. BannedMachines=" + string.Join(",", settings.BannedMachines.ToArray()) + ", replacementPrefab=" + ((Object)val).name + ", replaced=" + num + "."); return num; } private static GameObject FindBanReplacement(Dictionary prefabByMachine, ExternalSettings settings) { for (int i = 0; i < settings.Machines.Count; i++) { string text = settings.Machines[i]; if (!ContainsMachine(settings.BannedMachines, text) && prefabByMachine.TryGetValue(text, out var value) && (Object)(object)value != (Object)null) { return value; } } return null; } private static bool IsBannedPrefab(GameObject prefab, List bannedMachines) { if ((Object)(object)prefab == (Object)null || bannedMachines == null) { return false; } for (int i = 0; i < bannedMachines.Count; i++) { if (IsMachinePrefab(prefab, bannedMachines[i])) { return true; } } return false; } private static bool ContainsMachine(List machines, string machine) { if (machines == null) { return false; } string text = Normalize(machine); for (int i = 0; i < machines.Count; i++) { if (Normalize(machines[i]) == text) { return true; } } return false; } private static void LogFloorSummary(Dictionary totalByFloor, Dictionary changedByFloorAndMachine, List machines, int perFloorCount, int bannedReplaced) { List list = new List(totalByFloor.Keys); list.Sort(); int num = 0; foreach (int value in changedByFloorAndMachine.Values) { num += value; } Log("Machine assignment complete. Requested " + perFloorCount + " positions per machine per floor. Machines=" + string.Join(",", machines.ToArray()) + ". Assigned total: " + num + ". Banned replacements: " + bannedReplaced + "."); for (int i = 0; i < list.Count; i++) { int num2 = list[i]; List list2 = new List(); for (int j = 0; j < machines.Count; j++) { string text = machines[j]; list2.Add(text + "=" + GetCount(changedByFloorAndMachine, MakeFloorMachineKey(num2, text))); } Log("Floor " + num2 + ": positions=" + GetCount(totalByFloor, num2) + ", assigned " + string.Join(", ", list2.ToArray()) + "."); } } private static void Increment(Dictionary counts, int key) { counts[key] = GetCount(counts, key) + 1; } private static int GetCount(Dictionary counts, int key) { if (counts == null || !counts.TryGetValue(key, out var value)) { return 0; } return value; } private static void Increment(Dictionary counts, string key) { counts[key] = GetCount(counts, key) + 1; } private static int GetCount(Dictionary counts, string key) { if (counts == null || !counts.TryGetValue(key, out var value)) { return 0; } return value; } private static string MakeFloorMachineKey(int floor, string machine) { return floor + "|" + Normalize(machine); } private static string GetPositionKey(StampManager manager, ObjectStamp stamp) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) if (GetPositionKeyMethod == null || (Object)(object)stamp == (Object)null) { return null; } object obj = GetPositionKeyMethod.Invoke(manager, new object[2] { ((Component)stamp).transform.position, stamp.Floor }); return obj as string; } private static Dictionary FindConfiguredPrefabs(StampManager manager, Dictionary assigned, List machines) { Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); List catalog = CollectPrefabCatalog(manager, assigned); for (int i = 0; i < machines.Count; i++) { string text = machines[i]; GameObject val = FindMachinePrefab(catalog, text); if ((Object)(object)val != (Object)null) { dictionary[text] = val; Log("Machine key '" + text + "' matched prefab '" + ((Object)val).name + "'."); } else { Log("Machine key '" + text + "' was not found in current assignments or floor loot tables."); } } return dictionary; } private static List CollectPrefabCatalog(StampManager manager, Dictionary assigned) { List list = new List(); HashSet seen = new HashSet(); if (assigned != null) { foreach (GameObject value in assigned.Values) { AddPrefab(list, seen, value); } } if (!(FloorLootTablesField.GetValue(manager) is IList list2)) { return list; } for (int i = 0; i < list2.Count; i++) { object obj = list2[i]; if (obj != null) { FieldInfo field = obj.GetType().GetField("lootTable", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); object lootTableObject = ((field == null) ? null : field.GetValue(obj)); AddLootTablePrefabs(list, seen, lootTableObject); } } return list; } private static void AddLootTablePrefabs(List prefabs, HashSet seen, object lootTableObject) { if (lootTableObject == null) { return; } object memberValue = GetMemberValue(lootTableObject, "LootTable"); object memberValue2 = GetMemberValue(memberValue, "ObjectsToLoot"); if (memberValue2 is IList list) { for (int i = 0; i < list.Count; i++) { object target = list[i]; object memberValue3 = GetMemberValue(target, "Loot"); GameObject prefab = (GameObject)((memberValue3 is GameObject) ? memberValue3 : null); AddPrefab(prefabs, seen, prefab); } } } private static void AddPrefab(List prefabs, HashSet seen, GameObject prefab) { if (!((Object)(object)prefab == (Object)null) && !seen.Contains(prefab)) { seen.Add(prefab); prefabs.Add(prefab); } } private static GameObject FindMachinePrefab(List catalog, string machine) { for (int i = 0; i < catalog.Count; i++) { if (IsMachinePrefab(catalog[i], machine)) { return catalog[i]; } } return null; } private static object GetMemberValue(object target, string name) { if (target == null) { return null; } Type type = target.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (property != null) { return property.GetValue(target, null); } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field.GetValue(target); } return null; } private static bool IsMachinePrefab(GameObject prefab, string machine) { if ((Object)(object)prefab == (Object)null || string.IsNullOrEmpty(machine)) { return false; } string text = Normalize(machine); if (text.Length == 0) { return false; } string text2 = Normalize(((Object)prefab).name); if (text2 == text || text2.IndexOf(text, StringComparison.Ordinal) >= 0) { return true; } GameBase[] componentsInChildren = prefab.GetComponentsInChildren(true); foreach (GameBase val in componentsInChildren) { if (!((Object)(object)val == (Object)null)) { if (Normalize(((object)val).GetType().Name) == text) { return true; } if (Normalize(val.GameName) == text) { return true; } } } Component[] componentsInChildren2 = prefab.GetComponentsInChildren(true); foreach (Component val2 in componentsInChildren2) { if ((Object)(object)val2 != (Object)null && Normalize(((object)val2).GetType().Name) == text) { return true; } } return false; } private static string Normalize(string value) { if (string.IsNullOrEmpty(value)) { return ""; } char[] array = new char[value.Length]; int length = 0; foreach (char c in value) { if (char.IsLetterOrDigit(c)) { array[length++] = char.ToLowerInvariant(c); } } return new string(array, 0, length); } private static void Log(string message) { if ((Object)(object)GwyfMachineControlBuhePlugin.Instance != (Object)null) { GwyfMachineControlBuhePlugin.Instance.LogInfo(message); } else { Debug.Log((object)("[GWYF-MachineControl-BUHE] " + message)); } } }