Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BetterCasinoV2 v2.0.0
BetterCasino.dll
Decompiled 10 months agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using BetterCasino.Core; using BetterCasino.Features; using HarmonyLib; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.Casino; using Il2CppScheduleOne.Casino.UI; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.PlayerScripts; using Il2CppSystem.Collections.Generic; using Il2CppTMPro; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(Main), "BetterCasino", "2.0.0", "Zyhloh", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Zyhloh")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Better Casino mod for Schedule I")] [assembly: AssemblyFileVersion("2.0.0.0")] [assembly: AssemblyInformationalVersion("2.0.0")] [assembly: AssemblyProduct("BetterCasino")] [assembly: AssemblyTitle("BetterCasino")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("2.0.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace BetterCasino.Features { public static class AlwaysOpen { [HarmonyPatch(typeof(TimedAccessZone), "GetIsOpen")] public static class TimedAccessZone_GetIsOpen_Patch { public static bool Prefix(TimedAccessZone __instance, ref bool __result) { if ((Object)(object)__instance == (Object)null) { return true; } if (((Object)((Component)__instance).gameObject).name.Contains("Casino")) { __result = true; return false; } return true; } } [HarmonyPatch(typeof(TimedAccessZone), "MinPass")] public static class TimedAccessZone_MinPass_Patch { public static void Postfix(TimedAccessZone __instance) { if ((Object)(object)__instance == (Object)null) { return; } string name = ((Object)((Component)__instance).gameObject).name; if (!name.Contains("Casino")) { return; } int instanceID = ((Object)__instance).GetInstanceID(); if (!((AccessZone)__instance).IsOpen && InstanceFinder.IsHost) { ((AccessZone)__instance).SetIsOpen(true); ((MelonBase)Main.Instance).LoggerInstance.Msg("[HOST] Forced Casino open: " + name); } if (!_openedZones.Contains(instanceID) && InstanceFinder.IsHost) { _openedZones.Add(instanceID); if (((AccessZone)__instance).onOpen != null) { ((AccessZone)__instance).onOpen.Invoke(); ((MelonBase)Main.Instance).LoggerInstance.Msg("[HOST] Invoked onOpen for Casino: " + name); } } } } [HarmonyPatch(typeof(AccessZone), "SetIsOpen")] public static class AccessZone_SetIsOpen_Patch { public static void Prefix(AccessZone __instance, ref bool open) { if (!((Object)(object)__instance == (Object)null) && InstanceFinder.IsHost && ((Object)((Component)__instance).gameObject).name.Contains("Casino")) { open = true; } } } private static readonly HashSet<int> _openedZones = new HashSet<int>(); public static void Initialize() { ((MelonBase)Main.Instance).LoggerInstance.Msg("AlwaysOpen feature initialized."); } public static void OnSceneLoaded() { _openedZones.Clear(); } } public static class BetterSlots { [HarmonyPatch(typeof(SlotMachine), "RpcLogic___SetBetIndex_2681120339")] public static class SlotMachine_SetBetIndex_Patch { public static void Postfix(SlotMachine __instance, int index) { UpdateSlotLabel(__instance, index); } } [HarmonyPatch(typeof(SlotMachine), "UpInteracted")] public static class SlotMachine_UpInteracted_Patch { public static void Postfix(SlotMachine __instance) { UpdateSlotLabelFromCurrent(__instance); } } [HarmonyPatch(typeof(SlotMachine), "DownInteracted")] public static class SlotMachine_DownInteracted_Patch { public static void Postfix(SlotMachine __instance) { UpdateSlotLabelFromCurrent(__instance); } } [HarmonyPatch(typeof(BlackjackInterface), "Open")] public static class Blackjack_Open_Patch { public static void Postfix(BlackjackInterface __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.BetSlider != (Object)null) { __instance.BetSlider.minValue = 0f; __instance.BetSlider.maxValue = 100f; __instance.BetSlider.value = 0f; } } } [HarmonyPatch(typeof(BlackjackInterface), "BetSliderChanged")] public static class Blackjack_BetSliderChanged_Patch { public static void Postfix(BlackjackInterface __instance, float newValue) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.BetSlider != (Object)null) { __instance.BetSlider.SetValueWithoutNotify(newValue); } if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.BetAmount != (Object)null) { int amount = (int)(newValue * 1000f); ((TMP_Text)__instance.BetAmount).text = FormatMoney(amount); } if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.CurrentGame != (Object)null) { float localPlayerBet = newValue * 1000f; __instance.CurrentGame.LocalPlayerBet = localPlayerBet; } if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.ReadyButton != (Object)null) { ((Selectable)__instance.ReadyButton).interactable = true; } } } [HarmonyPatch(typeof(BlackjackInterface), "RefreshReadyButton")] public static class Blackjack_RefreshReadyButton_Patch { public static void Postfix(BlackjackInterface __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.ReadyButton != (Object)null) { ((Selectable)__instance.ReadyButton).interactable = true; } } } [HarmonyPatch(typeof(RTBInterface), "Open")] public static class RTB_Open_Patch { public static void Postfix(RTBInterface __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.BetSlider != (Object)null) { __instance.BetSlider.minValue = 0f; __instance.BetSlider.maxValue = 100f; __instance.BetSlider.value = 0f; } } } [HarmonyPatch(typeof(RTBInterface), "BetSliderChanged")] public static class RTB_BetSliderChanged_Patch { public static void Postfix(RTBInterface __instance, float newValue) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.BetSlider != (Object)null) { __instance.BetSlider.SetValueWithoutNotify(newValue); } if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.BetAmount != (Object)null) { int amount = (int)(newValue * 1000f); ((TMP_Text)__instance.BetAmount).text = FormatMoney(amount); } if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.CurrentGame != (Object)null) { float localPlayerBet = newValue * 1000f; __instance.CurrentGame.LocalPlayerBet = localPlayerBet; } if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.ReadyButton != (Object)null) { ((Selectable)__instance.ReadyButton).interactable = true; } } } [HarmonyPatch(typeof(RTBInterface), "RefreshReadyButton")] public static class RTB_RefreshReadyButton_Patch { public static void Postfix(RTBInterface __instance) { if ((Object)(object)__instance != (Object)null && (Object)(object)__instance.ReadyButton != (Object)null) { ((Selectable)__instance.ReadyButton).interactable = true; } } } private static bool _betAmountsSet = false; public static int TABLE_BET_MINIMUM = 1; public static int TABLE_BET_MAXIMUM = 100000; private static readonly int[] BaseBetIncrements = new int[19] { 0, 1, 5, 10, 25, 50, 100, 250, 500, 1000, 2500, 5000, 10000, 25000, 50000, 100000, 250000, 500000, 1000000 }; public static void Initialize() { ((MelonBase)Main.Instance).LoggerInstance.Msg("BetterSlots feature initialized."); } public static void OnSceneLoaded() { _betAmountsSet = false; } public static void OnUpdate() { if (!_betAmountsSet) { TrySetBetAmounts(); } } private static void TrySetBetAmounts() { try { int value = Settings.SlotMaxBet.Value; bool value2 = Settings.FreeBetEnabled.Value; List<int> list = new List<int>(); int[] baseBetIncrements = BaseBetIncrements; foreach (int num in baseBetIncrements) { if ((num != 0 || value2) && num <= value) { list.Add(num); } } if ((list.Count == 0 || list[list.Count - 1] < value) && !list.Contains(value)) { list.Add(value); } Il2CppStructArray<int> val = new Il2CppStructArray<int>((long)list.Count); for (int j = 0; j < list.Count; j++) { ((Il2CppArrayBase<int>)(object)val)[j] = list[j]; } SlotMachine.BetAmounts = val; _betAmountsSet = true; string value3 = (value2 ? "FREE" : "$1"); ((MelonBase)Main.Instance).LoggerInstance.Msg($"Set slot machine bet amounts: {value3} to {FormatMoney(value)} ({list.Count} increments)"); } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Msg("Failed to set bet amounts: " + ex.Message); _betAmountsSet = true; } } public static string FormatMoney(int amount) { if (amount == 0) { return "FREE"; } if (amount >= 1000000) { float num = (float)amount / 1000000f; if (num == (float)(int)num) { return $"${(int)num}M"; } return $"${num:0.#}M"; } if (amount >= 1000) { float num2 = (float)amount / 1000f; if (num2 == (float)(int)num2) { return $"${(int)num2}k"; } return $"${num2:0.#}k"; } return $"${amount}"; } private static void UpdateSlotLabel(SlotMachine instance, int index) { if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.BetAmountLabel == (Object)null) && SlotMachine.BetAmounts != null && index >= 0 && index < ((Il2CppArrayBase<int>)(object)SlotMachine.BetAmounts).Length) { int amount = ((Il2CppArrayBase<int>)(object)SlotMachine.BetAmounts)[index]; ((TMP_Text)instance.BetAmountLabel).text = FormatMoney(amount); ((TMP_Text)instance.BetAmountLabel).enableWordWrapping = false; ((TMP_Text)instance.BetAmountLabel).overflowMode = (TextOverflowModes)0; } } private static void UpdateSlotLabelFromCurrent(SlotMachine instance) { if (!((Object)(object)instance == (Object)null) && !((Object)(object)instance.BetAmountLabel == (Object)null)) { int currentBetAmount = instance.currentBetAmount; ((TMP_Text)instance.BetAmountLabel).text = FormatMoney(currentBetAmount); ((TMP_Text)instance.BetAmountLabel).enableWordWrapping = false; ((TMP_Text)instance.BetAmountLabel).overflowMode = (TextOverflowModes)0; } } } public static class CasinoSafehouse { private static readonly HashSet<int> _clearedPlayers = new HashSet<int>(); private static float _lastCheckTime = 0f; private const float CHECK_INTERVAL = 2f; private static Vector3 _casinoPosition = Vector3.zero; private static bool _casinoFound = false; private static bool _searchedForCasino = false; public static bool Enabled = true; public static void Initialize() { ((MelonBase)Main.Instance).LoggerInstance.Msg("CasinoSafehouse feature initialized."); } public static void OnSceneLoaded() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) _clearedPlayers.Clear(); _casinoPosition = Vector3.zero; _casinoFound = false; _searchedForCasino = false; } public static void OnUpdate() { if (Enabled && InstanceFinder.IsHost && !(Time.time - _lastCheckTime < 2f)) { _lastCheckTime = Time.time; if (!_searchedForCasino) { FindCasinoPosition(); _searchedForCasino = true; } if (_casinoFound) { CheckPlayersInCasino(); } } } private static void FindCasinoPosition() { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) try { Il2CppArrayBase<AccessZone> val = Object.FindObjectsOfType<AccessZone>(); if (val == null) { return; } foreach (AccessZone item in val) { if ((Object)(object)item != (Object)null && ((Object)((Component)item).gameObject).name.Contains("Casino")) { _casinoPosition = ((Component)item).transform.position; _casinoFound = true; ((MelonBase)Main.Instance).LoggerInstance.Msg($"Casino Safehouse: Found casino at {_casinoPosition}"); break; } } } catch { } } private static void CheckPlayersInCasino() { //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) try { List<Player> playerList = Player.PlayerList; if (playerList == null) { return; } float x = _casinoPosition.x; float y = _casinoPosition.y; float z = _casinoPosition.z; Enumerator<Player> enumerator = playerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if ((Object)(object)current == (Object)null || (Object)(object)((Component)current).transform == (Object)null) { continue; } int instanceID = ((Object)current).GetInstanceID(); Vector3 position = ((Component)current).transform.position; float num = position.x - x; float num2 = position.y - y; float num3 = position.z - z; if (num * num + num2 * num2 + num3 * num3 < 2500f) { if (!_clearedPlayers.Contains(instanceID)) { ClearPlayerWanted(current); _clearedPlayers.Add(instanceID); } } else { _clearedPlayers.Remove(instanceID); } } } catch { } } private static void ClearPlayerWanted(Player player) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) try { if (!((Object)(object)player == (Object)null)) { PlayerCrimeData crimeData = player.CrimeData; if (!((Object)(object)crimeData == (Object)null) && (int)crimeData.CurrentPursuitLevel != 0) { crimeData.SetPursuitLevel((EPursuitLevel)0); crimeData.ClearCrimes(); string text = ((Object)player).name ?? "Unknown"; ((MelonBase)Main.Instance).LoggerInstance.Msg("[HOST] Cleared wanted status for player entering Casino: " + text); } } } catch { } } } public static class SignChanger { private static int _signsChanged; private const int EXPECTED_SIGN_COUNT = 2; public static void Initialize() { ((MelonBase)Main.Instance).LoggerInstance.Msg("SignChanger feature initialized."); } public static void OnSceneLoaded() { _signsChanged = 0; } public static void OnUpdate() { if (_signsChanged < 2) { TryChangeSign(); } } private static void TryChangeSign() { foreach (TextMeshPro item in Object.FindObjectsOfType<TextMeshPro>()) { if ((Object)(object)item == (Object)null) { continue; } string text = ((TMP_Text)item).text; if (string.IsNullOrEmpty(text)) { continue; } bool flag = false; if (text.Contains("OPENING HOURS") || text.Contains("Opening Hours") || text.Contains("opening hours")) { Transform val = item.transform; while ((Object)(object)val != (Object)null) { if (((Object)val).name.Contains("Casino", StringComparison.OrdinalIgnoreCase)) { flag = true; break; } val = val.parent; } if (!flag && ((Object)((Component)item).gameObject).name.Contains("Casino", StringComparison.OrdinalIgnoreCase)) { flag = true; } } if (text.Contains("4PM") || text.Contains("5AM") || text.Contains("4 PM") || text.Contains("5 AM")) { Transform val2 = item.transform; while ((Object)(object)val2 != (Object)null) { if (((Object)val2).name.Contains("Casino", StringComparison.OrdinalIgnoreCase)) { flag = true; break; } val2 = val2.parent; } } if (flag) { string text2 = ((TMP_Text)item).text; ((TMP_Text)item).text = "24/7"; _signsChanged++; ((MelonBase)Main.Instance).LoggerInstance.Msg($"Changed Casino sign #{_signsChanged} from '{text2.Replace("\n", "\\n")}' to '24/7'"); ((MelonBase)Main.Instance).LoggerInstance.Msg("Sign GameObject: " + ((Object)((Component)item).gameObject).name); } } } } public static class SlotControl { private class PlayerSpinData { public Dictionary<int, float> MachineLastSpinTime = new Dictionary<int, float>(); public Dictionary<int, int> MachineWinStreaks = new Dictionary<int, int>(); public float LastActivityTime; } [HarmonyPatch(typeof(SlotMachine), "RpcLogic___SendStartSpin_2681120339")] public static class SlotMachine_SendStartSpin_Patch { public static void Prefix(SlotMachine __instance, NetworkConnection spinner, ref int betAmount) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (InstanceFinder.IsHost) { int num = ((spinner != (NetworkConnection)null) ? spinner.ClientId : 0); int instanceID = ((Object)__instance).GetInstanceID(); SlotMachine_GetRandomSymbol_Patch.SetSymbols(GenerateSymbolsForOutcome(DetermineOutcome(num, instanceID))); if (LogSpins) { ((MelonBase)Main.Instance).LoggerInstance.Msg($"[HOST] Slot outcome determined for player {num}"); } } } } [HarmonyPatch(typeof(SlotMachine), "GetRandomSymbol")] public static class SlotMachine_GetRandomSymbol_Patch { private static int _symbolIndex; private static Il2CppStructArray<ESymbol> _currentSymbols; public static void SetSymbols(Il2CppStructArray<ESymbol> symbols) { _currentSymbols = symbols; _symbolIndex = 0; } public static bool Prefix(ref ESymbol __result) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected I4, but got Unknown if (!InstanceFinder.IsHost) { return true; } if (_currentSymbols != null && _symbolIndex < ((Il2CppArrayBase<ESymbol>)(object)_currentSymbols).Length) { __result = (ESymbol)(int)((Il2CppArrayBase<ESymbol>)(object)_currentSymbols)[_symbolIndex]; _symbolIndex++; if (_symbolIndex >= ((Il2CppArrayBase<ESymbol>)(object)_currentSymbols).Length) { _currentSymbols = null; _symbolIndex = 0; } return false; } return true; } } private static readonly Dictionary<int, PlayerSpinData> _playerData = new Dictionary<int, PlayerSpinData>(); private static float _lastCleanupTime = 0f; public static float BaseJackpotChance = 0.01f; public static float BaseBigWinChance = 0.05f; public static float BaseSmallWinChance = 0.15f; public static float BaseMiniWinChance = 0.25f; public static float WinStreakPenaltyPerWin = 0.15f; public static int WinStreakThreshold = 2; public static float MultiMachinePenalty = 0.5f; public static float MultiMachineTimeWindow = 5f; public static bool EnableAntiAbuse = true; public static bool LogSpins = false; public static void Initialize() { ((MelonBase)Main.Instance).LoggerInstance.Msg("SlotControl feature initialized."); ((MelonBase)Main.Instance).LoggerInstance.Msg($" Base chances - Jackpot: {BaseJackpotChance * 100f}%, Big: {BaseBigWinChance * 100f}%, Small: {BaseSmallWinChance * 100f}%, Mini: {BaseMiniWinChance * 100f}%"); ((MelonBase)Main.Instance).LoggerInstance.Msg(" Anti-abuse: " + (EnableAntiAbuse ? "Enabled" : "Disabled")); } public static void OnSceneLoaded() { _playerData.Clear(); } public static void OnUpdate() { if (Time.time - _lastCleanupTime > 30f) { _lastCleanupTime = Time.time; CleanupOldPlayerData(); } } private static void CleanupOldPlayerData() { List<int> list = new List<int>(); float time = Time.time; foreach (KeyValuePair<int, PlayerSpinData> playerDatum in _playerData) { if (time - playerDatum.Value.LastActivityTime > 120f) { list.Add(playerDatum.Key); } } foreach (int item in list) { _playerData.Remove(item); } } private static PlayerSpinData GetPlayerData(int playerId) { if (!_playerData.ContainsKey(playerId)) { _playerData[playerId] = new PlayerSpinData(); } return _playerData[playerId]; } private static int CountActiveMachinesForPlayer(PlayerSpinData data) { float time = Time.time; int num = 0; foreach (KeyValuePair<int, float> item in data.MachineLastSpinTime) { if (time - item.Value < MultiMachineTimeWindow) { num++; } } return num; } private static float GetWinChanceMultiplier(int playerId, int machineId) { float num = 1f; if (!EnableAntiAbuse) { return num; } PlayerSpinData playerData = GetPlayerData(playerId); int num2 = CountActiveMachinesForPlayer(playerData); if (num2 > 1) { float val = 1f - MultiMachinePenalty * (float)(num2 - 1); num *= Math.Max(0.1f, val); if (LogSpins) { ((MelonBase)Main.Instance).LoggerInstance.Msg($"Player {playerId} multi-machine penalty: {num2} machines, multiplier: {num:F2}"); } } if (playerData.MachineWinStreaks.TryGetValue(machineId, out var value) && value >= WinStreakThreshold) { float val2 = 1f - WinStreakPenaltyPerWin * (float)(value - WinStreakThreshold + 1); num *= Math.Max(0.05f, val2); if (LogSpins) { ((MelonBase)Main.Instance).LoggerInstance.Msg($"Player {playerId} win streak penalty: {value} wins, multiplier: {num:F2}"); } } return num; } private static void RecordSpin(int playerId, int machineId, bool isWin) { PlayerSpinData playerData = GetPlayerData(playerId); playerData.LastActivityTime = Time.time; playerData.MachineLastSpinTime[machineId] = Time.time; if (isWin) { if (!playerData.MachineWinStreaks.ContainsKey(machineId)) { playerData.MachineWinStreaks[machineId] = 0; } playerData.MachineWinStreaks[machineId]++; } else { playerData.MachineWinStreaks[machineId] = 0; } } private static EOutcome DetermineOutcome(int playerId, int machineId) { //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Invalid comparison between Unknown and I4 //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) float winChanceMultiplier = GetWinChanceMultiplier(playerId, machineId); float value = Random.value; float num = BaseJackpotChance * winChanceMultiplier; float num2 = num + BaseBigWinChance * winChanceMultiplier; float num3 = num2 + BaseSmallWinChance * winChanceMultiplier; float num4 = num3 + BaseMiniWinChance * winChanceMultiplier; EOutcome val = ((value < num) ? ((EOutcome)0) : ((value < num2) ? ((EOutcome)1) : ((value < num3) ? ((EOutcome)2) : ((!(value < num4)) ? ((EOutcome)4) : ((EOutcome)3))))); bool isWin = (int)val != 4; RecordSpin(playerId, machineId, isWin); if (LogSpins) { ((MelonBase)Main.Instance).LoggerInstance.Msg($"Slot spin: roll={value:F3}, multiplier={winChanceMultiplier:F2}, outcome={val}"); } return val; } private static Il2CppStructArray<ESymbol> GenerateSymbolsForOutcome(EOutcome outcome) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected I4, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_0129: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0140: Unknown result type (might be due to invalid IL or missing references) Il2CppStructArray<ESymbol> val = new Il2CppStructArray<ESymbol>(3L); switch ((int)outcome) { case 0: ((Il2CppArrayBase<ESymbol>)(object)val)[0] = (ESymbol)5; ((Il2CppArrayBase<ESymbol>)(object)val)[1] = (ESymbol)5; ((Il2CppArrayBase<ESymbol>)(object)val)[2] = (ESymbol)5; break; case 1: { ESymbol val7 = (((Il2CppArrayBase<ESymbol>)(object)val)[1] = (((Il2CppArrayBase<ESymbol>)(object)val)[0] = (ESymbol)Random.Range(4, 6))); ((Il2CppArrayBase<ESymbol>)(object)val)[2] = val7; break; } case 2: { ESymbol val4 = (((Il2CppArrayBase<ESymbol>)(object)val)[1] = (((Il2CppArrayBase<ESymbol>)(object)val)[0] = (ESymbol)Random.Range(0, 4))); ((Il2CppArrayBase<ESymbol>)(object)val)[2] = val4; break; } case 3: ((Il2CppArrayBase<ESymbol>)(object)val)[0] = (ESymbol)Random.Range(0, 4); ((Il2CppArrayBase<ESymbol>)(object)val)[1] = (ESymbol)Random.Range(0, 4); ((Il2CppArrayBase<ESymbol>)(object)val)[2] = (ESymbol)Random.Range(0, 4); while (((Il2CppArrayBase<ESymbol>)(object)val)[0] == ((Il2CppArrayBase<ESymbol>)(object)val)[1] && ((Il2CppArrayBase<ESymbol>)(object)val)[1] == ((Il2CppArrayBase<ESymbol>)(object)val)[2]) { ((Il2CppArrayBase<ESymbol>)(object)val)[2] = (ESymbol)Random.Range(0, 4); } break; default: ((Il2CppArrayBase<ESymbol>)(object)val)[0] = (ESymbol)Random.Range(0, 6); ((Il2CppArrayBase<ESymbol>)(object)val)[1] = (ESymbol)Random.Range(0, 6); ((Il2CppArrayBase<ESymbol>)(object)val)[2] = (ESymbol)Random.Range(0, 6); while (((Il2CppArrayBase<ESymbol>)(object)val)[0] == ((Il2CppArrayBase<ESymbol>)(object)val)[1] && ((Il2CppArrayBase<ESymbol>)(object)val)[1] == ((Il2CppArrayBase<ESymbol>)(object)val)[2]) { ((Il2CppArrayBase<ESymbol>)(object)val)[2] = (ESymbol)Random.Range(0, 6); } while (IsFruitCombo(val)) { ((Il2CppArrayBase<ESymbol>)(object)val)[2] = (ESymbol)Random.Range(0, 6); } break; } return val; } private static bool IsFruitCombo(Il2CppStructArray<ESymbol> symbols) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Invalid comparison between Unknown and I4 //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if ((int)((Il2CppArrayBase<ESymbol>)(object)symbols)[0] <= 3 && (int)((Il2CppArrayBase<ESymbol>)(object)symbols)[1] <= 3) { return (int)((Il2CppArrayBase<ESymbol>)(object)symbols)[2] <= 3; } return false; } } } namespace BetterCasino.Core { public static class ConfigFile { public const int MIN_BET = 1; public const int MAX_SLOT_BET = 1000000; public const int MAX_TABLE_BET = 1000000; public const float MIN_CHANCE = 0f; public const float MAX_CHANCE = 1f; public const int MIN_STREAK_THRESHOLD = 1; public const int MAX_STREAK_THRESHOLD = 100; public const float MIN_TIME_WINDOW = 1f; public const float MAX_TIME_WINDOW = 60f; private static string ConfigPath => Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "..", "UserData", "BetterCasino.cfg"); private static int ClampInt(int value, int min, int max, string name) { if (value < min) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to minimum {min}"); return min; } if (value > max) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to maximum {max}"); return max; } return value; } private static float ClampFloat(float value, float min, float max, string name) { if (value < min) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to minimum {min}"); return min; } if (value > max) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to maximum {max}"); return max; } return value; } private static bool TryParseBool(string value, out bool result) { value = value.ToLower().Trim(); switch (value) { case "true": case "1": case "yes": case "on": result = true; return true; case "false": case "0": case "no": case "off": result = false; return true; default: result = false; return false; } } private static bool TryParseInt(string value, out int result) { return int.TryParse(value.Trim(), NumberStyles.Integer, CultureInfo.InvariantCulture, out result); } private static bool TryParseFloat(string value, out float result) { return float.TryParse(value.Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out result); } public static void CreateConfigFileIfNotExists() { try { string directoryName = Path.GetDirectoryName(ConfigPath); if (!Directory.Exists(directoryName)) { Directory.CreateDirectory(directoryName); } if (!File.Exists(ConfigPath)) { SaveConfigFile(); ((MelonBase)Main.Instance).LoggerInstance.Msg("Config file created at: " + ConfigPath); } } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Error("Failed to create config file: " + ex.Message); } } public static void SaveConfigFile() { try { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.AppendLine("# ═══════════════════════════════════════════════════════════════════════════════"); stringBuilder.AppendLine("# BetterCasino Configuration File"); stringBuilder.AppendLine("# ═══════════════════════════════════════════════════════════════════════════════"); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# This file is auto-generated by BetterCasino."); stringBuilder.AppendLine("# Edit the values below to customize the mod."); stringBuilder.AppendLine("# Changes take effect after restarting the game."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# If you have Mod Manager & Phone App installed, you can also"); stringBuilder.AppendLine("# change these settings in-game through the phone app or main menu."); stringBuilder.AppendLine("#"); stringBuilder.AppendLine("# ═══════════════════════════════════════════════════════════════════════════════"); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine("# GENERAL SETTINGS"); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Keep the casino open 24/7 (true/false)"); StringBuilder stringBuilder2 = stringBuilder; StringBuilder stringBuilder3 = stringBuilder2; StringBuilder.AppendInterpolatedStringHandler handler = new StringBuilder.AppendInterpolatedStringHandler(13, 1, stringBuilder2); handler.AppendLiteral("AlwaysOpen = "); handler.AppendFormatted(Settings.AlwaysOpenEnabled.Value.ToString().ToLower()); stringBuilder3.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Change casino signs to show \"24/7\" (true/false)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder4 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder2); handler.AppendLiteral("SignChanger = "); handler.AppendFormatted(Settings.SignChangerEnabled.Value.ToString().ToLower()); stringBuilder4.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Clear wanted status when entering the casino (true/false)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder5 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(18, 1, stringBuilder2); handler.AppendLiteral("CasinoSafehouse = "); handler.AppendFormatted(Settings.CasinoSafehouseEnabled.Value.ToString().ToLower()); stringBuilder5.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine("# SLOT MACHINE SETTINGS"); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Enable FREE ($0) bet option for slot machines (true/false)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder6 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder2); handler.AppendLiteral("FreeBetEnabled = "); handler.AppendFormatted(Settings.FreeBetEnabled.Value.ToString().ToLower()); stringBuilder6.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Maximum bet amount for slot machines (in dollars, max: 1000000)"); stringBuilder.AppendLine("# Bet increments are automatically generated: FREE, $1, $5, $10, $25, $50, $100,"); stringBuilder.AppendLine("# $250, $500, $1k, $2.5k, $5k, $10k, $25k, $50k, $100k, $250k, $500k, $1M"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder7 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(13, 1, stringBuilder2); handler.AppendLiteral("SlotMaxBet = "); handler.AppendFormatted(Settings.SlotMaxBet.Value); stringBuilder7.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine("# TABLE GAME SETTINGS (Blackjack & Ride The Bus)"); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Minimum bet for table games (in dollars, min: 1)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder8 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder2); handler.AppendLiteral("TableMinBet = "); handler.AppendFormatted(Settings.TableMinBet.Value); stringBuilder8.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Maximum bet for table games (in dollars, max: 1000000)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder9 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(14, 1, stringBuilder2); handler.AppendLiteral("TableMaxBet = "); handler.AppendFormatted(Settings.TableMaxBet.Value); stringBuilder9.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine("# ANTI-ABUSE SYSTEM (Slot Machines)"); stringBuilder.AppendLine("# ───────────────────────────────────────────────────────────────────────────────"); stringBuilder.AppendLine("# Prevents players from exploiting slot machines by playing multiple machines"); stringBuilder.AppendLine("# at once or farming win streaks."); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Enable the anti-abuse system (true/false)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder10 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(19, 1, stringBuilder2); handler.AppendLiteral("AntiAbuseEnabled = "); handler.AppendFormatted(Settings.AntiAbuseEnabled.Value.ToString().ToLower()); stringBuilder10.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ─── Win Chances (0.0 to 1.0, where 0.01 = 1%, max total should be < 1.0) ───"); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Base chance for JACKPOT (three 7s)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder11 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(16, 1, stringBuilder2); handler.AppendLiteral("JackpotChance = "); handler.AppendFormatted(Settings.JackpotChance.Value); stringBuilder11.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Base chance for BIG WIN (three bells or sevens)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder12 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(15, 1, stringBuilder2); handler.AppendLiteral("BigWinChance = "); handler.AppendFormatted(Settings.BigWinChance.Value); stringBuilder12.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Base chance for SMALL WIN (three matching fruits)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder13 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(17, 1, stringBuilder2); handler.AppendLiteral("SmallWinChance = "); handler.AppendFormatted(Settings.SmallWinChance.Value); stringBuilder13.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Base chance for MINI WIN (all fruits, not matching)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder14 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(16, 1, stringBuilder2); handler.AppendLiteral("MiniWinChance = "); handler.AppendFormatted(Settings.MiniWinChance.Value); stringBuilder14.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ─── Win Streak Penalty ───"); stringBuilder.AppendLine("# Reduces win chances when a player wins multiple times in a row."); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Number of consecutive wins before penalty kicks in"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder15 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(21, 1, stringBuilder2); handler.AppendLiteral("WinStreakThreshold = "); handler.AppendFormatted(Settings.WinStreakThreshold.Value); stringBuilder15.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Win chance reduction per consecutive win after threshold (0.15 = 15%)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder16 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(19, 1, stringBuilder2); handler.AppendLiteral("WinStreakPenalty = "); handler.AppendFormatted(Settings.WinStreakPenalty.Value); stringBuilder16.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ─── Multi-Machine Penalty ───"); stringBuilder.AppendLine("# Reduces win chances when a player uses multiple machines at once."); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Time window to track multi-machine usage (in seconds)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder17 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(25, 1, stringBuilder2); handler.AppendLiteral("MultiMachineTimeWindow = "); handler.AppendFormatted(Settings.MultiMachineTimeWindow.Value); stringBuilder17.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Win chance reduction per extra machine (0.5 = 50%)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder18 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(22, 1, stringBuilder2); handler.AppendLiteral("MultiMachinePenalty = "); handler.AppendFormatted(Settings.MultiMachinePenalty.Value); stringBuilder18.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ─── Debug ───"); stringBuilder.AppendLine(); stringBuilder.AppendLine("# Log slot spin results to console (true/false)"); stringBuilder2 = stringBuilder; StringBuilder stringBuilder19 = stringBuilder2; handler = new StringBuilder.AppendInterpolatedStringHandler(11, 1, stringBuilder2); handler.AppendLiteral("LogSpins = "); handler.AppendFormatted(Settings.LogSpins.Value.ToString().ToLower()); stringBuilder19.AppendLine(ref handler); stringBuilder.AppendLine(); stringBuilder.AppendLine("# ═══════════════════════════════════════════════════════════════════════════════"); stringBuilder.AppendLine("# END OF CONFIGURATION"); stringBuilder.AppendLine("# ═══════════════════════════════════════════════════════════════════════════════"); File.WriteAllText(ConfigPath, stringBuilder.ToString()); ((MelonBase)Main.Instance).LoggerInstance.Msg("Config file saved to: " + ConfigPath); } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Error("Failed to create config file: " + ex.Message); } } public static void LoadConfigFile() { if (!File.Exists(ConfigPath)) { ((MelonBase)Main.Instance).LoggerInstance.Msg("No config file found. Using default settings."); return; } try { string[] array = File.ReadAllLines(ConfigPath); foreach (string text in array) { if (string.IsNullOrWhiteSpace(text) || text.TrimStart().StartsWith("#")) { continue; } string[] array2 = text.Split(new char[1] { '=' }, 2); if (array2.Length != 2) { continue; } string text2 = array2[0].Trim(); string text3 = array2[1].Trim(); try { if (text2 == null) { continue; } switch (text2.Length) { case 10: switch (text2[0]) { case 'A': if (text2 == "AlwaysOpen") { if (TryParseBool(text3, out var result15)) { Settings.AlwaysOpenEnabled.Value = result15; } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid boolean for AlwaysOpen: '" + text3 + "', using default"); } } break; case 'S': if (text2 == "SlotMaxBet") { if (TryParseInt(text3, out var result14)) { Settings.SlotMaxBet.Value = ClampInt(result14, 1, 1000000, "SlotMaxBet"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for SlotMaxBet: '" + text3 + "', using default"); } } break; } break; case 11: switch (text2[0]) { case 'S': if (text2 == "SignChanger") { if (TryParseBool(text3, out var result11)) { Settings.SignChangerEnabled.Value = result11; } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid boolean for SignChanger: '" + text3 + "', using default"); } } break; case 'T': { int result10; if (!(text2 == "TableMinBet")) { if (text2 == "TableMaxBet") { if (TryParseInt(text3, out var result9)) { Settings.TableMaxBet.Value = ClampInt(result9, 1, 1000000, "TableMaxBet"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for TableMaxBet: '" + text3 + "', using default"); } } } else if (TryParseInt(text3, out result10)) { Settings.TableMinBet.Value = ClampInt(result10, 1, 1000000, "TableMinBet"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for TableMinBet: '" + text3 + "', using default"); } break; } } break; case 14: switch (text2[0]) { case 'F': if (text2 == "FreeBetEnabled") { if (TryParseBool(text3, out var result8)) { Settings.FreeBetEnabled.Value = result8; } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid boolean for FreeBetEnabled: '" + text3 + "', using default"); } } break; case 'S': if (text2 == "SmallWinChance") { if (TryParseFloat(text3, out var result7)) { Settings.SmallWinChance.Value = ClampFloat(result7, 0f, 1f, "SmallWinChance"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for SmallWinChance: '" + text3 + "', using default"); } } break; } break; case 16: switch (text2[0]) { case 'A': if (text2 == "AntiAbuseEnabled") { if (TryParseBool(text3, out var result3)) { Settings.AntiAbuseEnabled.Value = result3; } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid boolean for AntiAbuseEnabled: '" + text3 + "', using default"); } } break; case 'W': if (text2 == "WinStreakPenalty") { if (TryParseFloat(text3, out var result2)) { Settings.WinStreakPenalty.Value = ClampFloat(result2, 0f, 1f, "WinStreakPenalty"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for WinStreakPenalty: '" + text3 + "', using default"); } } break; } break; case 13: switch (text2[0]) { case 'J': if (text2 == "JackpotChance") { if (TryParseFloat(text3, out var result17)) { Settings.JackpotChance.Value = ClampFloat(result17, 0f, 1f, "JackpotChance"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for JackpotChance: '" + text3 + "', using default"); } } break; case 'M': if (text2 == "MiniWinChance") { if (TryParseFloat(text3, out var result16)) { Settings.MiniWinChance.Value = ClampFloat(result16, 0f, 1f, "MiniWinChance"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for MiniWinChance: '" + text3 + "', using default"); } } break; } break; case 15: if (text2 == "CasinoSafehouse") { if (TryParseBool(text3, out var result5)) { Settings.CasinoSafehouseEnabled.Value = result5; } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid boolean for CasinoSafehouse: '" + text3 + "', using default"); } } break; case 12: if (text2 == "BigWinChance") { if (TryParseFloat(text3, out var result12)) { Settings.BigWinChance.Value = ClampFloat(result12, 0f, 1f, "BigWinChance"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for BigWinChance: '" + text3 + "', using default"); } } break; case 18: if (text2 == "WinStreakThreshold") { if (TryParseInt(text3, out var result4)) { Settings.WinStreakThreshold.Value = ClampInt(result4, 1, 100, "WinStreakThreshold"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for WinStreakThreshold: '" + text3 + "', using default"); } } break; case 22: if (text2 == "MultiMachineTimeWindow") { if (TryParseFloat(text3, out var result13)) { Settings.MultiMachineTimeWindow.Value = ClampFloat(result13, 1f, 60f, "MultiMachineTimeWindow"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for MultiMachineTimeWindow: '" + text3 + "', using default"); } } break; case 19: if (text2 == "MultiMachinePenalty") { if (TryParseFloat(text3, out var result6)) { Settings.MultiMachinePenalty.Value = ClampFloat(result6, 0f, 1f, "MultiMachinePenalty"); } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid number for MultiMachinePenalty: '" + text3 + "', using default"); } } break; case 8: if (text2 == "LogSpins") { if (TryParseBool(text3, out var result)) { Settings.LogSpins.Value = result; } else { ((MelonBase)Main.Instance).LoggerInstance.Warning("Invalid boolean for LogSpins: '" + text3 + "', using default"); } } break; case 9: case 17: case 20: case 21: break; } } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Warning("Failed to parse config value '" + text2 + "': " + ex.Message); } } ((MelonBase)Main.Instance).LoggerInstance.Msg("Config file loaded successfully."); } catch (Exception ex2) { ((MelonBase)Main.Instance).LoggerInstance.Error("Failed to load config file: " + ex2.Message); } } } public class Main : MelonMod { public static Main Instance { get; private set; } public override void OnInitializeMelon() { Instance = this; ((MelonBase)this).LoggerInstance.Msg("BetterCasino v2.0.0 initialized!"); Settings.Initialize(); ConfigFile.LoadConfigFile(); AlwaysOpen.Initialize(); SignChanger.Initialize(); BetterSlots.Initialize(); SlotControl.Initialize(); CasinoSafehouse.Initialize(); Settings.ApplySettings(); ConfigFile.CreateConfigFileIfNotExists(); } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { ((MelonBase)this).LoggerInstance.Msg("Scene loaded: " + sceneName); AlwaysOpen.OnSceneLoaded(); SignChanger.OnSceneLoaded(); BetterSlots.OnSceneLoaded(); SlotControl.OnSceneLoaded(); CasinoSafehouse.OnSceneLoaded(); } public override void OnUpdate() { SignChanger.OnUpdate(); BetterSlots.OnUpdate(); SlotControl.OnUpdate(); CasinoSafehouse.OnUpdate(); } public override void OnLateUpdate() { } public override void OnGUI() { } public override void OnApplicationQuit() { Settings.Unsubscribe(); ((MelonBase)this).LoggerInstance.Msg("BetterCasino shutting down."); } } public static class Settings { private static MelonPreferences_Category _generalCategory; private static MelonPreferences_Category _slotsCategory; private static MelonPreferences_Category _tableGamesCategory; private static MelonPreferences_Category _antiAbuseCategory; public static MelonPreferences_Entry<bool> AlwaysOpenEnabled; public static MelonPreferences_Entry<bool> SignChangerEnabled; public static MelonPreferences_Entry<bool> FreeBetEnabled; public static MelonPreferences_Entry<int> SlotMaxBet; public static MelonPreferences_Entry<int> TableMinBet; public static MelonPreferences_Entry<int> TableMaxBet; public static MelonPreferences_Entry<bool> AntiAbuseEnabled; public static MelonPreferences_Entry<float> JackpotChance; public static MelonPreferences_Entry<float> BigWinChance; public static MelonPreferences_Entry<float> SmallWinChance; public static MelonPreferences_Entry<float> MiniWinChance; public static MelonPreferences_Entry<float> WinStreakPenalty; public static MelonPreferences_Entry<int> WinStreakThreshold; public static MelonPreferences_Entry<float> MultiMachinePenalty; public static MelonPreferences_Entry<float> MultiMachineTimeWindow; public static MelonPreferences_Entry<bool> LogSpins; public static MelonPreferences_Entry<bool> CasinoSafehouseEnabled; private static bool _modManagerFound; private static object _onPhoneEvent; private static object _onMenuEvent; private static Delegate _phoneHandler; private static Delegate _menuHandler; public static void Initialize() { _generalCategory = MelonPreferences.CreateCategory("BetterCasino_01_General", "General Settings"); _slotsCategory = MelonPreferences.CreateCategory("BetterCasino_02_SlotMachines", "Slot Machines"); _tableGamesCategory = MelonPreferences.CreateCategory("BetterCasino_03_TableGames", "Table Games"); _antiAbuseCategory = MelonPreferences.CreateCategory("BetterCasino_04_AntiAbuse", "Anti-Abuse System"); AlwaysOpenEnabled = _generalCategory.CreateEntry<bool>("AlwaysOpen", true, "Casino Always Open", "Keep the casino open 24/7", false, false, (ValueValidator)null, (string)null); SignChangerEnabled = _generalCategory.CreateEntry<bool>("SignChanger", true, "Change Signs to 24/7", "Update casino signs to show 24/7", false, false, (ValueValidator)null, (string)null); FreeBetEnabled = _slotsCategory.CreateEntry<bool>("FreeBetEnabled", true, "Enable FREE Bet Option", "Add a $0 FREE bet option to slot machines", false, false, (ValueValidator)null, (string)null); SlotMaxBet = _slotsCategory.CreateEntry<int>("MaxBet", 100000, "Maximum Bet Amount", "Maximum bet amount for slot machines", false, false, (ValueValidator)null, (string)null); TableMinBet = _tableGamesCategory.CreateEntry<int>("MinBet", 1, "Minimum Bet", "Minimum bet for Blackjack and RTB", false, false, (ValueValidator)null, (string)null); TableMaxBet = _tableGamesCategory.CreateEntry<int>("MaxBet", 100000, "Maximum Bet", "Maximum bet for Blackjack and RTB", false, false, (ValueValidator)null, (string)null); AntiAbuseEnabled = _antiAbuseCategory.CreateEntry<bool>("Enabled", true, "Enable Anti-Abuse", "Enable penalties for slot machine abuse", false, false, (ValueValidator)null, (string)null); JackpotChance = _antiAbuseCategory.CreateEntry<float>("JackpotChance", 0.01f, "Jackpot Chance", "Base chance for jackpot (0.01 = 1%)", false, false, (ValueValidator)null, (string)null); BigWinChance = _antiAbuseCategory.CreateEntry<float>("BigWinChance", 0.05f, "Big Win Chance", "Base chance for big win (0.05 = 5%)", false, false, (ValueValidator)null, (string)null); SmallWinChance = _antiAbuseCategory.CreateEntry<float>("SmallWinChance", 0.15f, "Small Win Chance", "Base chance for small win (0.15 = 15%)", false, false, (ValueValidator)null, (string)null); MiniWinChance = _antiAbuseCategory.CreateEntry<float>("MiniWinChance", 0.25f, "Mini Win Chance", "Base chance for mini win (0.25 = 25%)", false, false, (ValueValidator)null, (string)null); WinStreakPenalty = _antiAbuseCategory.CreateEntry<float>("WinStreakPenalty", 0.15f, "Win Streak Penalty", "Chance reduction per consecutive win (0.15 = 15%)", false, false, (ValueValidator)null, (string)null); WinStreakThreshold = _antiAbuseCategory.CreateEntry<int>("WinStreakThreshold", 2, "Win Streak Threshold", "Number of wins before penalty applies", false, false, (ValueValidator)null, (string)null); MultiMachinePenalty = _antiAbuseCategory.CreateEntry<float>("MultiMachinePenalty", 0.5f, "Multi-Machine Penalty", "Chance reduction per extra machine (0.5 = 50%)", false, false, (ValueValidator)null, (string)null); MultiMachineTimeWindow = _antiAbuseCategory.CreateEntry<float>("MultiMachineTimeWindow", 5f, "Multi-Machine Time Window", "Seconds to track multi-machine usage", false, false, (ValueValidator)null, (string)null); LogSpins = _antiAbuseCategory.CreateEntry<bool>("LogSpins", false, "Log Spin Results", "Log slot spin results to console for debugging", false, false, (ValueValidator)null, (string)null); CasinoSafehouseEnabled = _generalCategory.CreateEntry<bool>("CasinoSafehouse", true, "Casino Safehouse", "Clear wanted status when entering the casino", false, false, (ValueValidator)null, (string)null); ((MelonBase)Main.Instance).LoggerInstance.Msg("Settings initialized."); TrySubscribeToModManager(); } private static void TrySubscribeToModManager() { try { _modManagerFound = MelonBase.RegisteredMelons.Any(delegate(MelonBase mod) { object obj; if (mod == null) { obj = null; } else { MelonInfoAttribute info = mod.Info; obj = ((info != null) ? info.Name : null); } return (string?)obj == "Mod Manager & Phone App"; }); if (_modManagerFound) { ((MelonBase)Main.Instance).LoggerInstance.Msg("Mod Manager found. Subscribing to events..."); SubscribeToModManagerEvents_Helper(); } else { ((MelonBase)Main.Instance).LoggerInstance.Msg("Mod Manager not found. Settings will be loaded from config file."); } } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Error("Error checking for Mod Manager: " + ex.Message); _modManagerFound = false; } } private static void SubscribeToModManagerEvents_Helper() { try { Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault((Assembly a) => a.GetName().Name == "ModManager&PhoneApp"); if (assembly == null) { ((MelonBase)Main.Instance).LoggerInstance.Warning("Mod Manager assembly not found."); _modManagerFound = false; return; } Type type = assembly.GetType("ModManagerPhoneApp.ModSettingsEvents"); if (type == null) { ((MelonBase)Main.Instance).LoggerInstance.Warning("ModSettingsEvents type not found."); _modManagerFound = false; return; } EventInfo @event = type.GetEvent("OnPhonePreferencesSaved"); EventInfo event2 = type.GetEvent("OnMenuPreferencesSaved"); if (@event != null) { _phoneHandler = Delegate.CreateDelegate(@event.EventHandlerType, typeof(Settings).GetMethod("HandleSettingsUpdate", BindingFlags.Static | BindingFlags.NonPublic)); @event.AddEventHandler(null, _phoneHandler); ((MelonBase)Main.Instance).LoggerInstance.Msg("Subscribed to OnPhonePreferencesSaved."); } if (event2 != null) { _menuHandler = Delegate.CreateDelegate(event2.EventHandlerType, typeof(Settings).GetMethod("HandleSettingsUpdate", BindingFlags.Static | BindingFlags.NonPublic)); event2.AddEventHandler(null, _menuHandler); ((MelonBase)Main.Instance).LoggerInstance.Msg("Subscribed to OnMenuPreferencesSaved."); } _onPhoneEvent = @event; _onMenuEvent = event2; ((MelonBase)Main.Instance).LoggerInstance.Msg("Successfully subscribed to Mod Manager save events."); } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Error("Error subscribing to Mod Manager events: " + ex.Message); _modManagerFound = false; } } private static void HandleSettingsUpdate() { ((MelonBase)Main.Instance).LoggerInstance.Msg("Settings updated via Mod Manager. Applying changes..."); ApplySettings(); ConfigFile.SaveConfigFile(); } public static void ApplySettings() { SlotMaxBet.Value = ClampInt(SlotMaxBet.Value, 1, 1000000, "SlotMaxBet"); TableMinBet.Value = ClampInt(TableMinBet.Value, 1, 1000000, "TableMinBet"); TableMaxBet.Value = ClampInt(TableMaxBet.Value, 1, 1000000, "TableMaxBet"); JackpotChance.Value = ClampFloat(JackpotChance.Value, 0f, 1f, "JackpotChance"); BigWinChance.Value = ClampFloat(BigWinChance.Value, 0f, 1f, "BigWinChance"); SmallWinChance.Value = ClampFloat(SmallWinChance.Value, 0f, 1f, "SmallWinChance"); MiniWinChance.Value = ClampFloat(MiniWinChance.Value, 0f, 1f, "MiniWinChance"); WinStreakPenalty.Value = ClampFloat(WinStreakPenalty.Value, 0f, 1f, "WinStreakPenalty"); WinStreakThreshold.Value = ClampInt(WinStreakThreshold.Value, 1, 100, "WinStreakThreshold"); MultiMachinePenalty.Value = ClampFloat(MultiMachinePenalty.Value, 0f, 1f, "MultiMachinePenalty"); MultiMachineTimeWindow.Value = ClampFloat(MultiMachineTimeWindow.Value, 1f, 60f, "MultiMachineTimeWindow"); SlotControl.EnableAntiAbuse = AntiAbuseEnabled.Value; SlotControl.BaseJackpotChance = JackpotChance.Value; SlotControl.BaseBigWinChance = BigWinChance.Value; SlotControl.BaseSmallWinChance = SmallWinChance.Value; SlotControl.BaseMiniWinChance = MiniWinChance.Value; SlotControl.WinStreakPenaltyPerWin = WinStreakPenalty.Value; SlotControl.WinStreakThreshold = WinStreakThreshold.Value; SlotControl.MultiMachinePenalty = MultiMachinePenalty.Value; SlotControl.MultiMachineTimeWindow = MultiMachineTimeWindow.Value; SlotControl.LogSpins = LogSpins.Value; BetterSlots.TABLE_BET_MINIMUM = TableMinBet.Value; BetterSlots.TABLE_BET_MAXIMUM = TableMaxBet.Value; CasinoSafehouse.Enabled = CasinoSafehouseEnabled.Value; ((MelonBase)Main.Instance).LoggerInstance.Msg("Settings applied successfully."); } private static int ClampInt(int value, int min, int max, string name) { if (value < min) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to minimum {min}"); return min; } if (value > max) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to maximum {max}"); return max; } return value; } private static float ClampFloat(float value, float min, float max, string name) { if (value < min) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to minimum {min}"); return min; } if (value > max) { ((MelonBase)Main.Instance).LoggerInstance.Warning($"{name} was {value}, clamped to maximum {max}"); return max; } return value; } public static void Unsubscribe() { if (!_modManagerFound) { return; } try { if (_onPhoneEvent is EventInfo eventInfo && (object)_phoneHandler != null) { eventInfo.RemoveEventHandler(null, _phoneHandler); } if (_onMenuEvent is EventInfo eventInfo2 && (object)_menuHandler != null) { eventInfo2.RemoveEventHandler(null, _menuHandler); } ((MelonBase)Main.Instance).LoggerInstance.Msg("Unsubscribed from Mod Manager events."); } catch (Exception ex) { ((MelonBase)Main.Instance).LoggerInstance.Warning("Error during unsubscribe: " + ex.Message); } } } }