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<string> ParseMachineList(string value)
{
List<string> list = new List<string>();
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<string> 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<string> Machines = new List<string> { "Crash" };
internal List<string> BannedMachines = new List<string>();
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<string, GameObject> dictionary = PreAssignedGamesField.GetValue(__instance) as Dictionary<string, GameObject>;
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<string, GameObject> 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<ObjectStamp> sortedStamps = CollectSortedStamps(list);
Dictionary<int, int> totalByFloor = new Dictionary<int, int>();
Dictionary<int, List<ObjectStamp>> dictionary3 = GroupStampsByFloor(sortedStamps, totalByFloor);
Dictionary<string, int> dictionary4 = new Dictionary<string, int>();
HashSet<string> hashSet = new HashSet<string>();
List<int> list2 = new List<int>(dictionary3.Keys);
list2.Sort();
for (int i = 0; i < list2.Count; i++)
{
int num2 = list2[i];
List<ObjectStamp> 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<ObjectStamp> CollectSortedStamps(IList stamps)
{
List<ObjectStamp> list = new List<ObjectStamp>();
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<int, List<ObjectStamp>> GroupStampsByFloor(List<ObjectStamp> sortedStamps, Dictionary<int, int> totalByFloor)
{
Dictionary<int, List<ObjectStamp>> dictionary = new Dictionary<int, List<ObjectStamp>>();
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<ObjectStamp>());
}
value.Add(val);
}
}
return dictionary;
}
private static int ReplaceBannedMachines(StampManager manager, List<ObjectStamp> sortedStamps, Dictionary<string, GameObject> assigned, HashSet<string> forcedKeys, Dictionary<string, GameObject> 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<string, GameObject> 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<string> 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<string> 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<int, int> totalByFloor, Dictionary<string, int> changedByFloorAndMachine, List<string> machines, int perFloorCount, int bannedReplaced)
{
List<int> list = new List<int>(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<string> list2 = new List<string>();
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<int, int> counts, int key)
{
counts[key] = GetCount(counts, key) + 1;
}
private static int GetCount(Dictionary<int, int> counts, int key)
{
if (counts == null || !counts.TryGetValue(key, out var value))
{
return 0;
}
return value;
}
private static void Increment(Dictionary<string, int> counts, string key)
{
counts[key] = GetCount(counts, key) + 1;
}
private static int GetCount(Dictionary<string, int> 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<string, GameObject> FindConfiguredPrefabs(StampManager manager, Dictionary<string, GameObject> assigned, List<string> machines)
{
Dictionary<string, GameObject> dictionary = new Dictionary<string, GameObject>(StringComparer.OrdinalIgnoreCase);
List<GameObject> 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<GameObject> CollectPrefabCatalog(StampManager manager, Dictionary<string, GameObject> assigned)
{
List<GameObject> list = new List<GameObject>();
HashSet<GameObject> seen = new HashSet<GameObject>();
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<GameObject> prefabs, HashSet<GameObject> 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<GameObject> prefabs, HashSet<GameObject> seen, GameObject prefab)
{
if (!((Object)(object)prefab == (Object)null) && !seen.Contains(prefab))
{
seen.Add(prefab);
prefabs.Add(prefab);
}
}
private static GameObject FindMachinePrefab(List<GameObject> 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<GameBase>(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<Component>(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));
}
}
}