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 2 days 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); } } } }