Decompiled source of Chaos Night v0.6.4
BepInEx/plugins/ChaosNight/ChaosNight.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using ChaosNight.Core; using Extensions; using HarmonyLib; using Mirror; using MoreMountains.Feedbacks; using Newtonsoft.Json; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.AI; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.InputSystem; using UnityEngine.InputSystem.Controls; using UnityEngine.InputSystem.LowLevel; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: CompilationRelaxations(8)] [assembly: AssemblyVersion("0.0.0.0")] namespace ChaosNight { public static class AllInPolicy { public static bool CanExpand(bool enabled, bool available, bool reserved, bool correctTable, bool positiveBank, bool freeBet, bool percentageCalculation) { if (enabled && available && !reserved && correctTable && positiveBank && !freeBet) { return !percentageCalculation; } return false; } public static bool AcceptCharge(bool extraHand, bool outstandingAllIn, bool reserved, bool aboveOrdinaryCap, bool fullCurrentBank, bool available, bool authenticPlayer) { if (reserved || (extraHand && outstandingAllIn)) { return false; } if (!extraHand && fullCurrentBank) { if (available) { return authenticPlayer; } return false; } return !aboveOrdinaryCap; } public static bool Consume(bool nativeSuccess, bool reservedFullBank, bool extraHand) { if (nativeSuccess && reservedFullBank) { return !extraHand; } return false; } public static bool ExactNativeLong(bool nonnegative, bool withinLongRange, bool roundTrips) { if (nonnegative && withinLongRange) { return roundTrips; } return false; } } public sealed class ChaosAllIn : IDisposable { internal sealed class CallScope { internal GameBase Game; internal BigNumber Bet; internal bool Input; internal bool Extra; internal bool Percentage; } internal sealed class ChargeState { internal bool Reserved; internal GameBase Game; internal BigNumber PaidStake; } private sealed class LabelQuote { internal BigNumber Min; internal BigNumber Max; } internal static ChaosAllIn Active; private readonly Func<bool> allowed; private readonly Func<bool> available; private readonly Func<bool> consume; private readonly Func<PlayerProfile, bool> authentic; private readonly Action<string> log; private readonly HashSet<GameBase> outstanding = new HashSet<GameBase>(); private readonly Dictionary<Keypad, LabelQuote> labels = new Dictionary<Keypad, LabelQuote>(); private readonly List<Keypad> scratch = new List<Keypad>(); private bool disposed; private bool reserved; private bool reported; private bool faulted; [ThreadStatic] private static int ordinaryDepth; [ThreadStatic] internal static CallScope Scope; private static readonly FieldInfo MoneyInstance = AccessTools.Field(typeof(MoneyManager), "_instance"); private static readonly FieldInfo FreeBet = AccessTools.Field(typeof(GameBase), "isGoldenChipApplied"); private static readonly FieldInfo LabelText = AccessTools.Field(typeof(Keypad), "minMaxBetText"); private static readonly MethodInfo RouletteTotal = AccessTools.Method(typeof(Roulette), "GetTotalBet", Type.EmptyTypes, (Type[])null); private static readonly FieldInfo RouletteBets = AccessTools.Field(typeof(Roulette), "_bets"); private static readonly MethodInfo UpdateLabels = AccessTools.Method(typeof(Keypad), "UpdateMinMaxBetText", new Type[2] { typeof(BigNumber), typeof(BigNumber) }, (Type[])null); private static readonly MethodInfo RenderLabels = AccessTools.Method(typeof(Keypad), "RequestRender", Type.EmptyTypes, (Type[])null); private static readonly MethodInfo CurrentBetSetter = AccessTools.PropertySetter(typeof(GameBase), "currentBet"); private static readonly BigNumber RouletteSafeBank = BigNumber.FromDouble(4.993592041279217E+306); public ChaosAllIn(Func<bool> featureAllowed, Func<bool> permissionAvailable, Func<bool> consumePermission, Func<PlayerProfile, bool> authenticPlayer, Action<string> logger) { if (featureAllowed == null || permissionAvailable == null || consumePermission == null || authenticPlayer == null) { throw new ArgumentNullException("callback"); } if (Active != null) { throw new InvalidOperationException("BET IT ALL is already initialized."); } if (!NativeApiAvailable()) { throw new InvalidOperationException("Verified BET IT ALL native API is unavailable."); } allowed = featureAllowed; available = permissionAvailable; consume = consumePermission; authentic = authenticPlayer; log = logger; Active = this; } private bool Enabled() { if (!disposed && !faulted) { return allowed(); } return false; } private MoneyManager Money() { object? value = MoneyInstance.GetValue(null); return (MoneyManager)((value is MoneyManager) ? value : null); } private bool Table(GameBase game) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)game == (Object)null || !((Component)game).gameObject.activeInHierarchy) { return false; } Scene activeScene = SceneManager.GetActiveScene(); if (!((Scene)(ref activeScene)).IsValid() || !((Scene)(ref activeScene)).isLoaded || ((Scene)(ref activeScene)).name != "CasinoScene") { return false; } NetworkIdentity netIdentity = ((NetworkBehaviour)game).netIdentity; if ((Object)(object)netIdentity == (Object)null || netIdentity.netId == 0 || !object.ReferenceEquals(((Component)netIdentity).GetComponent<GameBase>(), game)) { return false; } NetworkIdentity value; if (!NetworkServer.active) { if (NetworkClient.active && ((NetworkBehaviour)game).isClient && NetworkClient.spawned.TryGetValue(netIdentity.netId, out value)) { return object.ReferenceEquals(value, netIdentity); } return false; } if (((NetworkBehaviour)game).isServer && NetworkServer.spawned.TryGetValue(netIdentity.netId, out value)) { return object.ReferenceEquals(value, netIdentity); } return false; } private bool IsFree(GameBase game) { return (bool)FreeBet.GetValue(game); } private bool AmountSupported(GameBase game, BigNumber amount) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Unknown result type (might be due to invalid IL or missing references) if (game is Roulette) { return amount <= RouletteSafeBank; } if (!(game is Plinko)) { return true; } BigNumber val = BigNumber.op_Implicit(long.MaxValue); if (amount < BigNumber.Zero || amount > val) { return false; } BigNumber val2 = BigNumber.op_Implicit(((BigNumber)(ref amount)).ToLong()); return AllInPolicy.ExactNativeLong(nonnegative: true, withinLongRange: true, val2 == amount); } private string AmountMessage(GameBase game) { if (!(game is Plinko)) { return "BET IT ALL: roulette payout would overflow."; } return "BET IT ALL: use whole dollars up to 9.22E18 on Plinko."; } private BigNumber OrdinaryMax(GameBase game) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) ordinaryDepth++; try { return game.MaxBet; } finally { ordinaryDepth--; } } private bool OrdinaryRoulette(GameBase game, BigNumber cap) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_004d: Unknown result type (might be due to invalid IL or missing references) if (!(game is Roulette)) { return false; } if (!(RouletteBets.GetValue(game) is Dictionary<string, BigNumber> { Count: <=128 } dictionary)) { return false; } foreach (BigNumber value in dictionary.Values) { if (value > cap || value < BigNumber.Zero) { return false; } } return true; } internal void MaxBet(GameBase game, ref BigNumber result) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0069: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) //IL_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Unknown result type (might be due to invalid IL or missing references) if (ordinaryDepth != 0 || disposed || faulted) { return; } try { MoneyManager val = Money(); if ((!((Object)(object)val != (Object)null) || AmountSupported(game, val.balance)) && AllInPolicy.CanExpand(Enabled(), available(), reserved, Table(game), (Object)(object)val != (Object)null && val.balance > BigNumber.Zero, (Object)(object)game == (Object)null || IsFree(game), Scope != null && Scope.Percentage)) { BigNumber balance = val.balance; if (Scope == null || !object.ReferenceEquals(Scope.Game, game) || (!Scope.Input && Scope.Extra) || !(Scope.Bet != balance)) { result = BigNumber.Max(result, balance); } } } catch (Exception error) { Report(error); } } internal CallScope EnterGame(GameBase game, bool extra) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Unknown result type (might be due to invalid IL or missing references) CallScope scope = Scope; BigNumber bet = BigNumber.Zero; try { if ((Object)(object)game != (Object)null) { bet = (BigNumber)((game is Roulette) ? ((BigNumber)RouletteTotal.Invoke(game, null)) : game.currentBet); } } catch (Exception error) { Report(error); } CallScope callScope = new CallScope(); callScope.Game = game; callScope.Bet = bet; callScope.Extra = extra; Scope = callScope; return scope; } internal bool BeforeStart(GameBase game) { //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00aa: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00c1: Unknown result type (might be due to invalid IL or missing references) //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_0122: Unknown result type (might be due to invalid IL or missing references) //IL_0128: Unknown result type (might be due to invalid IL or missing references) //IL_012d: Unknown result type (might be due to invalid IL or missing references) //IL_00f9: Unknown result type (might be due to invalid IL or missing references) //IL_0137: Unknown result type (might be due to invalid IL or missing references) //IL_015e: Unknown result type (might be due to invalid IL or missing references) //IL_0164: Unknown result type (might be due to invalid IL or missing references) if (disposed || !NetworkServer.active || (Object)(object)game == (Object)null) { return true; } try { if (Scope != null && Scope.Extra && outstanding.Contains(game)) { Reject(game, "BET IT ALL cannot double or split."); return false; } if (!Enabled() || !Table(game) || IsFree(game) || game.isPlaying || Scope == null || Scope.Extra) { return true; } MoneyManager val = Money(); if ((Object)(object)val == (Object)null) { return true; } bool flag = val.balance > BigNumber.Zero && Scope.Bet == val.balance; if (flag && !available()) { Reject(game, "BET IT ALL used. Choose a smaller bet."); return false; } if (flag && !AmountSupported(game, val.balance)) { Reject(game, AmountMessage(game)); return false; } BigNumber val2 = OrdinaryMax(game); if (Scope.Bet <= val2 || OrdinaryRoulette(game, val2)) { return true; } if (!reserved && available() && Scope.Bet == val.balance) { return true; } Reject(game, available() ? "BET IT ALL: press ALL for the full crew bank." : "BET IT ALL used. Choose a normal bet."); return false; } catch (Exception error) { Report(error); return false; } } private void Reject(GameBase game, string message) { Keypad val = (((Object)(object)game == (Object)null) ? null : game.Keypad); if (!((Object)(object)val == (Object)null) && ((NetworkBehaviour)val).isServer) { val.ServerInvalidBetAmountFb(); val.Network_errorMessage = message; RenderLabels.Invoke(val, null); } } internal CallScope EnterInput(Keypad keypad, string input) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) CallScope scope = Scope; CallScope callScope = new CallScope(); callScope.Game = (((Object)(object)keypad == (Object)null) ? null : keypad.casinoGame); callScope.Bet = BigNumber.Parse(input); callScope.Input = true; Scope = callScope; return scope; } internal CallScope EnterPercentage(Keypad keypad) { CallScope scope = Scope; CallScope callScope = new CallScope(); callScope.Game = (((Object)(object)keypad == (Object)null) ? null : keypad.casinoGame); callScope.Percentage = true; Scope = callScope; return scope; } internal void Percentage(Keypad keypad, decimal percentage, bool bankCap, ref BigNumber result) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00b5: Unknown result type (might be due to invalid IL or missing references) //IL_00ba: Unknown result type (might be due to invalid IL or missing references) if (disposed || faulted || !NetworkServer.active || !bankCap || percentage != 1m) { return; } try { GameBase val = (((Object)(object)keypad == (Object)null) ? null : keypad.casinoGame); MoneyManager val2 = Money(); if ((!((Object)(object)val2 != (Object)null) || AmountSupported(val, val2.balance)) && AllInPolicy.CanExpand(Enabled(), available(), reserved, Table(val), (Object)(object)val2 != (Object)null && val2.balance > BigNumber.Zero, (Object)(object)val == (Object)null || IsFree(val), percentageCalculation: false)) { result = val2.balance; } } catch (Exception error) { Report(error); } } internal bool BeforeAllButton(Keypad keypad) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) if (disposed || !NetworkServer.active || (Object)(object)keypad == (Object)null) { return true; } try { GameBase casinoGame = keypad.casinoGame; MoneyManager val = Money(); if (Enabled() && available() && Table(casinoGame) && !IsFree(casinoGame) && (Object)(object)val != (Object)null && !AmountSupported(casinoGame, val.balance)) { Reject(casinoGame, AmountMessage(casinoGame)); return false; } return true; } catch (Exception error) { Report(error); return false; } } internal bool BeforeCharge(BigNumber amount, PlayerProfile player, ChangeType kind, out ChargeState state) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_00cb: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: 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_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Unknown result type (might be due to invalid IL or missing references) //IL_00dc: 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_00e3: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f1: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013b: Unknown result type (might be due to invalid IL or missing references) //IL_0146: Unknown result type (might be due to invalid IL or missing references) //IL_0118: Unknown result type (might be due to invalid IL or missing references) //IL_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01c6: Unknown result type (might be due to invalid IL or missing references) state = null; if (disposed || !NetworkServer.active || (int)kind != 0 || amount >= BigNumber.Zero || Scope == null || Scope.Input || Scope.Percentage || (Object)(object)Scope.Game == (Object)null) { return true; } try { GameBase game = Scope.Game; bool extra = Scope.Extra; if (extra && outstanding.Contains(game)) { Reject(game, "BET IT ALL cannot double or split."); return false; } if (!Enabled() || !Table(game) || IsFree(game)) { return true; } MoneyManager val = Money(); if ((Object)(object)val == (Object)null) { return false; } BigNumber val2 = -amount; BigNumber balance = val.balance; BigNumber val3 = OrdinaryMax(game); bool flag = balance > BigNumber.Zero && val2 == balance; bool flag2 = available(); if (!extra && flag && flag2 && !AmountSupported(game, balance)) { Reject(game, AmountMessage(game)); return false; } bool aboveOrdinaryCap = !extra && val2 > val3 && !OrdinaryRoulette(game, val3); if (!AllInPolicy.AcceptCharge(extra, outstandingAllIn: false, reserved, aboveOrdinaryCap, flag, flag2, (Object)(object)player != (Object)null && authentic(player))) { Reject(game, "BET IT ALL needs the current full crew bank."); return false; } if (extra || !flag || !flag2) { return true; } reserved = true; state = new ChargeState { Reserved = true, Game = game, PaidStake = val2 }; return true; } catch (Exception error) { Report(error); return false; } } internal void AfterCharge(ChargeState state, bool success) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) if (state == null || !AllInPolicy.Consume(success, state.Reserved, extraHand: false)) { return; } try { outstanding.Add(state.Game); if (!consume()) { throw new InvalidOperationException("Paid BET IT ALL permission could not be committed."); } CurrentBetSetter.Invoke(state.Game, new object[1] { state.PaidStake }); } catch (Exception error) { Report(error); } } internal void FinishCharge(ChargeState state) { if (state != null && state.Reserved) { reserved = false; RefreshLabels(); } } internal void ForgetGame(GameBase game) { if (!object.ReferenceEquals(game, null)) { outstanding.Remove(game); } } internal void ForgetKeypad(Keypad keypad) { if (!object.ReferenceEquals(keypad, null)) { labels.Remove(keypad); } } internal void Label(Keypad keypad, BigNumber min, BigNumber max) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_010a: Unknown result type (might be due to invalid IL or missing references) if (disposed || (Object)(object)keypad == (Object)null) { return; } try { if (labels.Count < 512 || labels.ContainsKey(keypad)) { labels[keypad] = new LabelQuote { Min = min, Max = max }; } GameBase casinoGame = keypad.casinoGame; MoneyManager val = Money(); object? value = LabelText.GetValue(keypad); TextMeshPro val2 = (TextMeshPro)((value is TextMeshPro) ? value : null); if (!((Object)(object)val2 == (Object)null) && Enabled() && available() && !reserved && Table(casinoGame) && !((Object)(object)val == (Object)null) && !(val.balance <= BigNumber.Zero) && !IsFree(casinoGame) && AmountSupported(casinoGame, val.balance)) { string text = ((TMP_Text)val2).text; int num = text?.LastIndexOf('\n') ?? (-1); if (num >= 0) { ((TMP_Text)val2).text = text.Substring(0, num + 1) + "BET IT ALL: " + MoneyFormatter.FormatWithDollar(val.balance, false); RenderLabels.Invoke(keypad, null); } } } catch (Exception error) { Report(error); } } public void RefreshLabels() { //IL_00bb: 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) if (disposed) { return; } if (NetworkServer.active && ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.RefreshAll(); } scratch.Clear(); foreach (Keypad key in labels.Keys) { scratch.Add(key); } try { foreach (Keypad item in scratch) { if ((Object)(object)item == (Object)null) { labels.Remove(item); continue; } LabelQuote labelQuote = labels[item]; UpdateLabels.Invoke(item, new object[2] { labelQuote.Min, labelQuote.Max }); RenderLabels.Invoke(item, null); } } catch (Exception error) { Report(error); } finally { scratch.Clear(); } } private void Report(Exception error) { faulted = true; if (reported) { return; } reported = true; if (log == null) { return; } try { log("BET IT ALL held after a native integration error: " + error.GetBaseException().Message); } catch { } } private static bool NativeApiAvailable() { if (MoneyInstance != null && MoneyInstance.IsStatic && FreeBet != null && FreeBet.FieldType == typeof(bool) && LabelText != null && LabelText.FieldType == typeof(TextMeshPro) && RouletteTotal != null && RouletteTotal.ReturnType == typeof(BigNumber) && RouletteBets != null && RouletteBets.FieldType == typeof(Dictionary<string, BigNumber>) && UpdateLabels != null && RenderLabels != null && CurrentBetSetter != null && AccessTools.PropertyGetter(typeof(GameBase), "MaxBet") != null) { return AccessTools.Method(typeof(Keypad), "GetPercentageBet", new Type[2] { typeof(decimal), typeof(bool) }, (Type[])null) != null; } return false; } public static string SelfTest() { if (!NativeApiAvailable()) { throw new InvalidOperationException("BET IT ALL native API changed."); } if (!AllInPolicy.AcceptCharge(extraHand: false, outstandingAllIn: false, reserved: false, aboveOrdinaryCap: true, fullCurrentBank: true, available: true, authenticPlayer: true) || AllInPolicy.AcceptCharge(extraHand: false, outstandingAllIn: false, reserved: false, aboveOrdinaryCap: true, fullCurrentBank: false, available: true, authenticPlayer: true) || AllInPolicy.AcceptCharge(extraHand: true, outstandingAllIn: true, reserved: false, aboveOrdinaryCap: false, fullCurrentBank: false, available: true, authenticPlayer: true) || AllInPolicy.Consume(nativeSuccess: false, reservedFullBank: true, extraHand: false)) { throw new InvalidOperationException("BET IT ALL charge policy failed."); } return "PASS: BET IT ALL native API and full-bankroll charge policy (offline)"; } public void Dispose() { disposed = true; outstanding.Clear(); labels.Clear(); scratch.Clear(); reserved = false; if (object.ReferenceEquals(Active, this)) { Active = null; } } } [HarmonyPatch(typeof(GameBase), "get_MaxBet")] internal static class AllInMaxBetPatch { private static void Postfix(GameBase __instance, ref BigNumber __result) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.MaxBet(__instance, ref __result); } } } [HarmonyPatch] internal static class AllInStartScopePatch { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(GameBase), "TryStartGame", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Poker), "TryStartGame", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Roulette), "TryStartGame", (Type[])null, (Type[])null); } private static bool Prefix(GameBase __instance, out ChaosAllIn.CallScope __state) { __state = ChaosAllIn.Scope; if (ChaosAllIn.Active == null) { return true; } ChaosAllIn.Active.EnterGame(__instance, extra: false); return ChaosAllIn.Active.BeforeStart(__instance); } private static Exception Finalizer(ChaosAllIn.CallScope __state, Exception __exception) { ChaosAllIn.Scope = __state; return __exception; } } [HarmonyPatch] internal static class AllInExtraScopePatch { private static IEnumerable<MethodBase> TargetMethods() { yield return AccessTools.Method(typeof(Blackjack), "PlayerDouble", (Type[])null, (Type[])null); yield return AccessTools.Method(typeof(Blackjack), "PlayerSplit", (Type[])null, (Type[])null); } private static bool Prefix(GameBase __instance, out ChaosAllIn.CallScope __state) { __state = ChaosAllIn.Scope; if (ChaosAllIn.Active == null) { return true; } ChaosAllIn.Active.EnterGame(__instance, extra: true); return ChaosAllIn.Active.BeforeStart(__instance); } private static Exception Finalizer(ChaosAllIn.CallScope __state, Exception __exception) { ChaosAllIn.Scope = __state; return __exception; } } [HarmonyPatch(typeof(Keypad), "ApplyInput")] internal static class AllInInputScopePatch { private static void Prefix(Keypad __instance, string __0, out ChaosAllIn.CallScope __state) { __state = ChaosAllIn.Scope; if (ChaosAllIn.Active != null) { ChaosAllIn.Active.EnterInput(__instance, __0); } } private static Exception Finalizer(ChaosAllIn.CallScope __state, Exception __exception) { ChaosAllIn.Scope = __state; return __exception; } } [HarmonyPatch(typeof(Keypad), "GetPercentageBet")] internal static class AllInPercentagePatch { private static void Prefix(Keypad __instance, out ChaosAllIn.CallScope __state) { __state = ChaosAllIn.Scope; if (ChaosAllIn.Active != null) { ChaosAllIn.Active.EnterPercentage(__instance); } } private static void Postfix(Keypad __instance, decimal __0, bool __1, ref BigNumber __result) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.Percentage(__instance, __0, __1, ref __result); } } private static Exception Finalizer(ChaosAllIn.CallScope __state, Exception __exception) { ChaosAllIn.Scope = __state; return __exception; } } [HarmonyPatch(typeof(Keypad), "SetBetAll")] internal static class AllInAllButtonPatch { private static bool Prefix(Keypad __instance) { if (ChaosAllIn.Active != null) { return ChaosAllIn.Active.BeforeAllButton(__instance); } return true; } } [HarmonyPatch(typeof(MoneyManager), "TryChangeBalance")] internal static class AllInPaidChargePatch { [HarmonyPriority(800)] private static bool Prefix(BigNumber amount, PlayerProfile changer, ChangeType changeType, ref bool __result, out ChaosAllIn.ChargeState __state) { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Unknown result type (might be due to invalid IL or missing references) __state = null; if (ChaosAllIn.Active == null || ChaosAllIn.Active.BeforeCharge(amount, changer, changeType, out __state)) { return true; } __result = false; return false; } private static void Postfix(ChaosAllIn.ChargeState __state, bool __result) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.AfterCharge(__state, __result); } } private static Exception Finalizer(ChaosAllIn.ChargeState __state, Exception __exception) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.FinishCharge(__state); } return __exception; } } [HarmonyPatch(typeof(GameBase), "ResetGame")] internal static class AllInResetPatch { private static void Postfix(GameBase __instance) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.ForgetGame(__instance); } } } [HarmonyPatch(typeof(GameBase), "OnDisable")] internal static class AllInDisablePatch { private static void Prefix(GameBase __instance) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.ForgetGame(__instance); } } } [HarmonyPatch(typeof(Keypad), "OnDisable")] internal static class AllInKeypadDisablePatch { private static void Prefix(Keypad __instance) { if (ChaosAllIn.Active != null) { ChaosAllIn.Active.ForgetKeypad(__instance); } } } [HarmonyPatch(typeof(Keypad), "UpdateMinMaxBetText")] internal static class AllInLabelPatch { private static void Postfix(Keypad __instance, BigNumber __0, BigNumber __1) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_000e: Unknown result type (might be due to invalid IL or missing references) if (ChaosAllIn.Active != null) { ChaosAllIn.Active.Label(__instance, __0, __1); } } } public static class BedWakePolicy { public static bool CanStand(bool enabled, bool clientReady, bool localOwner, bool home, bool nativeWakeScope, bool spawnBoxInput, bool hasBody, bool nativeComponentsReady) { if (enabled && clientReady && localOwner && home && nativeWakeScope && spawnBoxInput && hasBody) { return nativeComponentsReady; } return false; } public static bool SameOwner(object expected, object actual) { if (expected != null) { return object.ReferenceEquals(expected, actual); } return false; } } internal static class BedWakeCallScope<T> where T : class { [ThreadStatic] internal static T Current; internal static T Enter(T owner) { T current = Current; Current = owner; return current; } internal static void Restore(T previous) { Current = previous; } } public sealed class ChaosBedWake : IDisposable { internal static ChaosBedWake Active; private readonly Func<bool> canUse; private readonly Action<string> log; private bool disposed; private bool reported; private static readonly FieldInfo Waking = AccessTools.Field(typeof(PlayerController), "_isWakingUp"); private static readonly FieldInfo Body = AccessTools.Field(typeof(PlayerController), "_rb"); private static readonly FieldInfo Carry = AccessTools.Field(typeof(PlayerController), "_pc"); private static readonly MethodInfo FreeState = AccessTools.Method(typeof(PlayerController), "SetPlayerState", new Type[1] { typeof(PlayerState) }, (Type[])null); public ChaosBedWake(Func<bool> canUseNativeFeatures, Action<string> logger) { if (canUseNativeFeatures == null) { throw new ArgumentNullException("canUseNativeFeatures"); } if (Active != null) { throw new InvalidOperationException("Native bed wake is already initialized."); } if (!NativeApiAvailable()) { throw new InvalidOperationException("Verified native bed-wake API is unavailable."); } canUse = canUseNativeFeatures; log = logger; Active = this; } internal bool TryStand(PlayerController controller) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) //IL_0025: 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_00d0: Invalid comparison between Unknown and I4 //IL_0178: Unknown result type (might be due to invalid IL or missing references) //IL_0185: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Unknown result type (might be due to invalid IL or missing references) //IL_0193: Unknown result type (might be due to invalid IL or missing references) if (disposed || (Object)(object)controller == (Object)null || !BedWakePolicy.SameOwner(BedWakeCallScope<PlayerController>.Current, controller)) { return false; } try { Scene activeScene = SceneManager.GetActiveScene(); NetworkIdentity netIdentity = ((NetworkBehaviour)controller).netIdentity; NetworkIdentity value; bool localOwner = (Object)(object)NetworkClient.localPlayer != (Object)null && ((NetworkBehaviour)controller).isLocalPlayer && BedWakePolicy.SameOwner(NetworkClient.localPlayer, netIdentity) && BedWakePolicy.SameOwner(((Component)netIdentity).GetComponent<PlayerController>(), controller) && netIdentity.netId != 0 && NetworkClient.spawned.TryGetValue(netIdentity.netId, out value) && BedWakePolicy.SameOwner(value, netIdentity); bool home = ((Scene)(ref activeScene)).IsValid() && ((Scene)(ref activeScene)).isLoaded && ((Scene)(ref activeScene)).name == "HomeScene"; if (!BedWakePolicy.CanStand(canUse(), NetworkClient.active && NetworkClient.ready, localOwner, home, nativeWakeScope: true, (int)InputEvents.ActiveLayer == 2, controller.hasBody, nativeComponentsReady: true)) { return false; } object? value2 = Body.GetValue(controller); Rigidbody val = (Rigidbody)((value2 is Rigidbody) ? value2 : null); object? value3 = Carry.GetValue(controller); PlayerCarry val2 = (PlayerCarry)((value3 is PlayerCarry) ? value3 : null); if (!((Component)controller).gameObject.activeInHierarchy || !((Behaviour)controller).isActiveAndEnabled || (Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)controller.head == (Object)null || !BedWakePolicy.SameOwner(val, ((Component)controller).GetComponent<Rigidbody>()) || !BedWakePolicy.SameOwner(val2, ((Component)controller).GetComponent<PlayerCarry>())) { return false; } Waking.SetValue(controller, true); InputEvents.ActiveLayer = (InputLayer)0; controller.IsLocked = false; PlayerState state = controller.State; controller.State = (PlayerState)0; if ((int)state == 0) { FreeState.Invoke(controller, new object[1] { (object)(PlayerState)0 }); } return true; } catch (Exception error) { Report(error); return false; } } private void Report(Exception error) { if (reported) { return; } reported = true; if (log == null) { return; } try { log("Standing bed wake held; native wake retained: " + error.GetBaseException().Message); } catch { } } private static bool NativeApiAvailable() { if (Waking != null && Waking.FieldType == typeof(bool) && !Waking.IsStatic && Body != null && Body.FieldType == typeof(Rigidbody) && Carry != null && Carry.FieldType == typeof(PlayerCarry) && FreeState != null && !FreeState.IsStatic && FreeState.ReturnType == typeof(void) && AccessTools.Method(typeof(PlayerController), "WakeUp", Type.EmptyTypes, (Type[])null) != null && AccessTools.Method(typeof(PlayerController), "UserCode_TargetWakeUp__NetworkConnection", new Type[1] { typeof(NetworkConnection) }, (Type[])null) != null) { return AccessTools.Method(typeof(PlayerController), "DelayedWakeUp", Type.EmptyTypes, (Type[])null) != null; } return false; } public static string SelfTest() { if (!NativeApiAvailable()) { throw new InvalidOperationException("Native bed-wake API shape changed."); } if (!BedWakePolicy.CanStand(enabled: true, clientReady: true, localOwner: true, home: true, nativeWakeScope: true, spawnBoxInput: true, hasBody: true, nativeComponentsReady: true) || BedWakePolicy.CanStand(enabled: true, clientReady: true, localOwner: false, home: true, nativeWakeScope: true, spawnBoxInput: true, hasBody: true, nativeComponentsReady: true) || BedWakePolicy.CanStand(enabled: true, clientReady: true, localOwner: true, home: false, nativeWakeScope: true, spawnBoxInput: true, hasBody: true, nativeComponentsReady: true) || BedWakePolicy.CanStand(enabled: true, clientReady: true, localOwner: true, home: true, nativeWakeScope: false, spawnBoxInput: true, hasBody: true, nativeComponentsReady: true) || BedWakePolicy.CanStand(enabled: true, clientReady: true, localOwner: true, home: true, nativeWakeScope: true, spawnBoxInput: true, hasBody: false, nativeComponentsReady: true)) { throw new InvalidOperationException("Native bed-wake scope policy failed."); } return "PASS: native standing bed-wake API and local-owner/Home/target scope (offline)"; } public void Dispose() { disposed = true; if (object.ReferenceEquals(Active, this)) { Active = null; } } } [HarmonyPatch(typeof(PlayerController), "UserCode_TargetWakeUp__NetworkConnection")] internal static class BedWakeNativeTargetScopePatch { private static void Prefix(PlayerController __instance, out PlayerController __state) { __state = BedWakeCallScope<PlayerController>.Enter(__instance); } private static Exception Finalizer(Exception __exception, PlayerController __state) { BedWakeCallScope<PlayerController>.Restore(__state); return __exception; } } [HarmonyPatch(typeof(PlayerController), "WakeUp")] internal static class BedWakeStandingPatch { private static bool Prefix(PlayerController __instance) { if (ChaosBedWake.Active != null) { return !ChaosBedWake.Active.TryStand(__instance); } return true; } } public static class BetDisplayPolicy { public static bool CanRefresh(bool enabled, bool server, bool casino, bool gameManager, bool gameState, bool liveKeypad, bool liveGame, bool correctBinding) { if (enabled && server && casino && gameManager && gameState && liveKeypad && liveGame) { return correctBinding; } return false; } public static bool LimitsChanged(bool hasPrevious, double oldMin, long oldMinExponent, double oldMax, long oldMaxExponent, double newMin, long newMinExponent, double newMax, long newMaxExponent) { if (hasPrevious && oldMin == newMin && oldMinExponent == newMinExponent && oldMax == newMax) { return oldMaxExponent != newMaxExponent; } return true; } } public sealed class ChaosBetDisplay : IDisposable { private sealed class Quote { internal GameBase Game; internal bool HasPrevious; internal double Min; internal double Max; internal long MinExponent; internal long MaxExponent; } private const int MaximumTrackedKeypads = 512; internal static ChaosBetDisplay Active; private readonly Func<bool> canRefresh; private readonly Action<string> log; private readonly Dictionary<Keypad, Quote> keypads = new Dictionary<Keypad, Quote>(); private readonly List<Keypad> scratch = new List<Keypad>(); private bool disposed; private bool refreshing; private bool reported; private static readonly FieldInfo ManagerInstance = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly MethodInfo RequestRender = AccessTools.Method(typeof(Keypad), "RequestRender", Type.EmptyTypes, (Type[])null); public ChaosBetDisplay(Func<bool> refreshAllowed, Action<string> logger) { if (refreshAllowed == null) { throw new ArgumentNullException("refreshAllowed"); } if (Active != null) { throw new InvalidOperationException("Native bet display is already initialized."); } if (!NativeApiAvailable()) { throw new InvalidOperationException("Verified native bet-display API is unavailable."); } canRefresh = refreshAllowed; log = logger; Active = this; } public void RefreshAll() { if (disposed || refreshing || !NetworkServer.active) { return; } refreshing = true; try { scratch.Clear(); foreach (Keypad key in keypads.Keys) { scratch.Add(key); } foreach (Keypad item in scratch) { if ((Object)(object)item == (Object)null || !((Component)item).gameObject.activeInHierarchy) { keypads.Remove(item); } else { RefreshOne(item, force: false); } } } catch (Exception error) { Report(error); } finally { scratch.Clear(); refreshing = false; } } internal void Observe(Keypad keypad, bool force) { if (disposed || (Object)(object)keypad == (Object)null) { return; } try { if (!keypads.TryGetValue(keypad, out var _)) { if (keypads.Count >= 512) { return; } keypads.Add(keypad, new Quote()); } if (force) { keypads[keypad].HasPrevious = false; } RefreshOne(keypad, force); } catch (Exception error) { Report(error); } } internal void Forget(Keypad keypad) { if (!disposed && !object.ReferenceEquals(keypad, null)) { keypads.Remove(keypad); } } private void RefreshOne(Keypad keypad, bool force) { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Invalid comparison between Unknown and I4 //IL_0101: Unknown result type (might be due to invalid IL or missing references) //IL_0106: Unknown result type (might be due to invalid IL or missing references) //IL_0109: Unknown result type (might be due to invalid IL or missing references) //IL_010e: Unknown result type (might be due to invalid IL or missing references) //IL_016a: Unknown result type (might be due to invalid IL or missing references) //IL_016c: Unknown result type (might be due to invalid IL or missing references) if (disposed || !NetworkServer.active || !canRefresh() || (Object)(object)keypad == (Object)null) { return; } Scene activeScene = SceneManager.GetActiveScene(); object? value = ManagerInstance.GetValue(null); GameManager val = (GameManager)((value is GameManager) ? value : null); GameBase casinoGame = keypad.casinoGame; bool correctBinding = (Object)(object)casinoGame != (Object)null && object.ReferenceEquals(casinoGame.Keypad, keypad); if (BetDisplayPolicy.CanRefresh(enabled: true, server: true, ((Scene)(ref activeScene)).IsValid() && ((Scene)(ref activeScene)).isLoaded && ((Scene)(ref activeScene)).name == "CasinoScene", (Object)(object)val != (Object)null, (Object)(object)val != (Object)null && ((NetworkBehaviour)val).isServer && (int)val.state == 1, ((NetworkBehaviour)keypad).isServer && ((Component)keypad).gameObject.activeInHierarchy && ((Behaviour)keypad).isActiveAndEnabled, (Object)(object)casinoGame != (Object)null && ((NetworkBehaviour)casinoGame).isServer && ((Component)casinoGame).gameObject.activeInHierarchy, correctBinding) && keypads.TryGetValue(keypad, out var value2)) { BigNumber minBet = casinoGame.MinBet; BigNumber maxBet = casinoGame.MaxBet; if (force || !object.ReferenceEquals(value2.Game, casinoGame) || BetDisplayPolicy.LimitsChanged(value2.HasPrevious, value2.Min, value2.MinExponent, value2.Max, value2.MaxExponent, minBet.Mantissa, minBet.Exponent, maxBet.Mantissa, maxBet.Exponent)) { keypad.ServerUpdateMinMaxBetText(minBet, maxBet); value2.Game = casinoGame; value2.HasPrevious = true; value2.Min = minBet.Mantissa; value2.MinExponent = minBet.Exponent; value2.Max = maxBet.Mantissa; value2.MaxExponent = maxBet.Exponent; } } } internal void Render(Keypad keypad) { if (disposed || (Object)(object)keypad == (Object)null || !NetworkClient.active) { return; } try { if (canRefresh()) { RequestRender.Invoke(keypad, null); } } catch (Exception error) { Report(error); } } private void Report(Exception error) { if (reported) { return; } reported = true; if (log == null) { return; } try { log("Native bet-label refresh held: " + error.GetBaseException().Message); } catch { } } private static bool NativeApiAvailable() { if (ManagerInstance != null && ManagerInstance.IsStatic && RequestRender != null && RequestRender.ReturnType == typeof(void) && AccessTools.PropertyGetter(typeof(GameBase), "MinBet") != null && AccessTools.PropertyGetter(typeof(GameBase), "MaxBet") != null && AccessTools.Method(typeof(Keypad), "ServerUpdateMinMaxBetText", new Type[2] { typeof(BigNumber), typeof(BigNumber) }, (Type[])null) != null && AccessTools.Method(typeof(Keypad), "UserCode_RpcSyncMinMaxBetLabels__Double__Int64__Double__Int64", new Type[4] { typeof(double), typeof(long), typeof(double), typeof(long) }, (Type[])null) != null) { return AccessTools.PropertySetter(typeof(GameManager), "currentQuota") != null; } return false; } public static string SelfTest() { if (!NativeApiAvailable()) { throw new InvalidOperationException("Native bet-display API shape changed."); } if (!BetDisplayPolicy.CanRefresh(enabled: true, server: true, casino: true, gameManager: true, gameState: true, liveKeypad: true, liveGame: true, correctBinding: true) || BetDisplayPolicy.CanRefresh(enabled: true, server: false, casino: true, gameManager: true, gameState: true, liveKeypad: true, liveGame: true, correctBinding: true) || !BetDisplayPolicy.LimitsChanged(hasPrevious: true, 4.8, 1L, 4.8, 2L, 4.1, 1L, 4.1, 2L) || BetDisplayPolicy.LimitsChanged(hasPrevious: true, 4.1, 1L, 4.1, 2L, 4.1, 1L, 4.1, 2L)) { throw new InvalidOperationException("Native bet-display refresh policy failed."); } return "PASS: native bet-display API and host quote refresh policy (offline)"; } public void Dispose() { disposed = true; keypads.Clear(); scratch.Clear(); if (object.ReferenceEquals(Active, this)) { Active = null; } } } [HarmonyPatch(typeof(GameManager), "set_currentQuota")] internal static class BetDisplayQuotaPatch { private static void Postfix() { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.RefreshAll(); } } } [HarmonyPatch(typeof(GameManager), "StartDay")] internal static class BetDisplayDayPatch { private static void Postfix() { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.RefreshAll(); } } } [HarmonyPatch(typeof(Keypad), "OnEnable")] internal static class BetDisplayEnablePatch { private static void Postfix(Keypad __instance) { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.Observe(__instance, force: true); } } } [HarmonyPatch(typeof(Keypad), "OnCasinoGameSet")] internal static class BetDisplayBindingPatch { private static void Postfix(Keypad __instance) { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.Observe(__instance, force: true); ChaosBetDisplay.Active.Render(__instance); } } } [HarmonyPatch(typeof(Keypad), "ApplyInput")] internal static class BetDisplayInputPatch { private static void Prefix(Keypad __instance) { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.Observe(__instance, force: false); } } } [HarmonyPatch(typeof(Keypad), "OnDisable")] internal static class BetDisplayDisablePatch { private static void Prefix(Keypad __instance) { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.Forget(__instance); } } } [HarmonyPatch(typeof(Keypad), "UserCode_RpcSyncMinMaxBetLabels__Double__Int64__Double__Int64")] internal static class BetDisplayLabelsReceivedPatch { private static void Postfix(Keypad __instance) { if (ChaosBetDisplay.Active != null) { ChaosBetDisplay.Active.Render(__instance); } } } public enum BodyRepairDisposition { Eligible, InvalidIdentity, NotReady, IncompleteSetup, AlreadyWhole, TemporarilyBusy } public sealed class BodyRepairProof { public bool CurrentConnection; public bool Authenticated; public bool Ready; public bool OwnedSpawnedCharacter; public bool CurrentOrgans; public bool ActiveCharacter; public bool NativeReferencesReady; public bool NativeDataBound; public bool Sleeping; public bool Waking; public bool MovementLocked; public bool LeftEye; public bool RightEye; public bool Body; public bool Mouth; } public static class BodyRepairPolicy { public const int MaximumPlayers = 32; public static bool CanStart(bool enabled, bool server, bool actualHost, bool validPhase, bool partyReady, bool sceneReady, bool resetting, bool shredderProcessing) { if (enabled && server && actualHost && validPhase && partyReady && sceneReady && !resetting) { return !shredderProcessing; } return false; } public static BodyRepairDisposition Classify(BodyRepairProof proof) { if (proof == null || !proof.CurrentConnection || !proof.Authenticated || !proof.OwnedSpawnedCharacter || !proof.CurrentOrgans) { return BodyRepairDisposition.InvalidIdentity; } if (!proof.Ready || !proof.ActiveCharacter) { return BodyRepairDisposition.NotReady; } if (!proof.NativeReferencesReady || !proof.NativeDataBound) { return BodyRepairDisposition.IncompleteSetup; } if (proof.LeftEye && proof.RightEye && proof.Body && proof.Mouth) { return BodyRepairDisposition.AlreadyWhole; } if (proof.Sleeping || proof.Waking || proof.MovementLocked) { return BodyRepairDisposition.TemporarilyBusy; } return BodyRepairDisposition.Eligible; } } public sealed class ChaosBodyRepair : IDisposable { private sealed class NativeBusy { public bool Sleeping; public bool Locked; public float WakeUntil; } private sealed class Target { public NetworkConnectionToClient Connection; public PlayerOrgans Organs; public PlayerProfile Profile; } private sealed class Plan { public OrganManager Manager; public readonly List<Target> Targets = new List<Target>(); public int Whole; public int Busy; public int Incomplete; } internal static ChaosBodyRepair Active; private readonly Func<bool> canUse; private readonly Func<List<NetworkConnectionToClient>> readyParty; private readonly Action<string> log; private readonly List<BodyShreddingMachine> machines = new List<BodyShreddingMachine>(); private readonly Dictionary<PlayerController, NativeBusy> busy = new Dictionary<PlayerController, NativeBusy>(); private readonly HashSet<string> errors = new HashSet<string>(); private bool disposed; private bool applying; private bool trackingOverflow; private static readonly FieldInfo ManagerInstance = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly FieldInfo OrganInstance = AccessTools.Field(typeof(OrganManager), "_instance"); private static readonly FieldInfo Transitioning = AccessTools.Field(typeof(GameManager), "_isTransitioning"); private static readonly FieldInfo SceneReady = AccessTools.Field(typeof(GameManager), "_scenePlayReady"); private static readonly FieldInfo ExpectedPlayers = AccessTools.Field(typeof(GameManager), "_expectedScenePlayers"); private static readonly FieldInfo SteamOrgans = AccessTools.Field(typeof(OrganManager), "OrganDataBySteamId"); private static readonly FieldInfo LeftModel = AccessTools.Field(typeof(PlayerOrgans), "leftEyeModel"); private static readonly FieldInfo RightModel = AccessTools.Field(typeof(PlayerOrgans), "rightEyeModel"); private static readonly FieldInfo BodyModel = AccessTools.Field(typeof(PlayerOrgans), "bodyModel"); private static readonly FieldInfo MouthModel = AccessTools.Field(typeof(PlayerOrgans), "mouthModel"); private static readonly FieldInfo OrganController = AccessTools.Field(typeof(PlayerOrgans), "_pc"); private static readonly FieldInfo OrganProfile = AccessTools.Field(typeof(PlayerOrgans), "_pp"); private static readonly FieldInfo OrganRigidbody = AccessTools.Field(typeof(PlayerOrgans), "_rb"); private static readonly FieldInfo OrganDrag = AccessTools.Field(typeof(PlayerOrgans), "_cd"); private static readonly FieldInfo OrganSettings = AccessTools.Field(typeof(PlayerOrgans), "_ps"); private static readonly FieldInfo OrganEyesUI = AccessTools.Field(typeof(PlayerOrgans), "_pe"); private static readonly FieldInfo LocalWaking = AccessTools.Field(typeof(PlayerController), "_isWakingUp"); private static readonly FieldInfo Processing = AccessTools.Field(typeof(BodyShreddingMachine), "_isProcessing"); public ChaosBodyRepair(Func<bool> nativeFeaturesAllowed, Func<List<NetworkConnectionToClient>> readyNativeParty, Action<string> logger) { if (nativeFeaturesAllowed == null || readyNativeParty == null) { throw new ArgumentNullException("Body repair guards"); } if (Active != null) { throw new InvalidOperationException("Body repair already initialized."); } if (!NativeApiAvailable()) { throw new InvalidOperationException("Verified native body repair API is unavailable."); } canUse = nativeFeaturesAllowed; readyParty = readyNativeParty; log = logger; Active = this; } public bool CanRepair(out string note) { try { Plan plan; return BuildPlan(out plan, out note); } catch (Exception error) { Report("body repair availability", error); note = "Player organ data is not ready yet."; return false; } } public bool TryRepair(out string note) { if (!BuildPlanSafe(out var plan, out note)) { return false; } applying = true; int num = 0; int num2 = 0; int num3 = 0; try { foreach (Target target in plan.Targets) { try { if (!FeaturesEnabled() || !CurrentOwned(target.Connection, target.Profile, target.Organs)) { num3++; continue; } BodyRepairProof proof = Proof(plan.Manager, target.Connection, target.Profile, target.Organs); if (BodyRepairPolicy.Classify(proof) != BodyRepairDisposition.Eligible) { num3++; continue; } plan.Manager.SetOrganDataBySteamId(target.Profile.steamId, true, true, true, true); if (BoundData(plan.Manager, target.Connection, target.Profile, target.Organs, out var steam, out var current2) && Whole(steam) && Whole(current2)) { num++; continue; } num2++; Report("body repair confirmation", new InvalidOperationException("Native organ records did not confirm this character's repair.")); } catch (Exception error) { num2++; Report("native body repair", error); } } } finally { applying = false; } note = "Repaired body parts for " + num + " player" + ((num == 1) ? "" : "s") + "."; if (plan.Whole > 0) { object obj = note; note = string.Concat(obj, " ", plan.Whole, " already whole."); } if (plan.Busy + num3 > 0) { object obj2 = note; note = string.Concat(obj2, " ", plan.Busy + num3, " skipped while busy or changing state."); } if (plan.Incomplete > 0) { object obj3 = note; note = string.Concat(obj3, " ", plan.Incomplete, " skipped until organ data finishes syncing."); } if (num2 > 0) { object obj4 = note; note = string.Concat(obj4, " ", num2, " could not be confirmed."); } if (num > 0) { Write(note); } return num > 0; } private bool BuildPlanSafe(out Plan plan, out string note) { try { return BuildPlan(out plan, out note); } catch (Exception error) { plan = null; note = "Player organ data is not ready yet."; Report("body repair preflight", error); return false; } } private bool BuildPlan(out Plan plan, out string note) { //IL_009c: Unknown result type (might be due to invalid IL or missing references) //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_00d9: Unknown result type (might be due to invalid IL or missing references) //IL_00f4: Unknown result type (might be due to invalid IL or missing references) //IL_00fa: Invalid comparison between Unknown and I4 plan = null; note = "Only the current host can repair the party."; if (disposed || applying || !NetworkServer.active || NetworkServer.localConnection == null) { return false; } NetworkConnectionToClient localConnection = (NetworkConnectionToClient)(object)NetworkServer.localConnection; if ((Object)(object)((NetworkConnection)localConnection).identity == (Object)null || !object.ReferenceEquals(NetworkClient.localPlayer, ((NetworkConnection)localConnection).identity)) { return false; } note = "Wait until every player is loaded and the room is compatible."; if (!FeaturesEnabled()) { return false; } List<NetworkConnectionToClient> list = readyParty(); if (!ExactParty(list, localConnection)) { return false; } object? value = ManagerInstance.GetValue(null); GameManager val = (GameManager)((value is GameManager) ? value : null); object? value2 = OrganInstance.GetValue(null); OrganManager val2 = (OrganManager)((value2 is OrganManager) ? value2 : null); Scene activeScene = SceneManager.GetActiveScene(); bool flag = (Object)(object)val != (Object)null && ((NetworkBehaviour)val).isServer && (Object)(object)val2 != (Object)null && ((NetworkBehaviour)val2).isServer && ((((Scene)(ref activeScene)).name == "HomeScene" && (int)val.state == 0) || (((Scene)(ref activeScene)).name == "CasinoScene" && (int)val.state == 1)); note = "Wait until native scene loading or the reset finishes."; if (!flag || (bool)Transitioning.GetValue(val) || !NativeSceneReady(val, list)) { return false; } Prune(list); note = "Wait until the body-part machine finishes processing."; if (trackingOverflow || RemovalBusy()) { return false; } if (!BodyRepairPolicy.CanStart(enabled: true, server: true, actualHost: true, flag, partyReady: true, sceneReady: true, resetting: false, shredderProcessing: false)) { return false; } plan = new Plan { Manager = val2 }; foreach (NetworkConnectionToClient item in list) { PlayerProfile component = ((Component)((NetworkConnection)item).identity).GetComponent<PlayerProfile>(); PlayerOrgans component2 = ((Component)((NetworkConnection)item).identity).GetComponent<PlayerOrgans>(); switch (BodyRepairPolicy.Classify(Proof(val2, item, component, component2))) { case BodyRepairDisposition.Eligible: plan.Targets.Add(new Target { Connection = item, Profile = component, Organs = component2 }); break; case BodyRepairDisposition.AlreadyWhole: plan.Whole++; break; case BodyRepairDisposition.TemporarilyBusy: plan.Busy++; break; default: plan.Incomplete++; break; } } if (plan.Targets.Count > 0) { note = "Repair missing body parts for " + plan.Targets.Count + " player" + ((plan.Targets.Count == 1) ? "" : "s") + "."; if (plan.Busy + plan.Incomplete > 0) { object obj = note; note = string.Concat(obj, " ", plan.Busy + plan.Incomplete, " will be skipped until ready."); } return true; } note = ((plan.Busy > 0) ? "Wait for sleeping, waking or movement-locked players to finish before repairing them." : ((plan.Incomplete > 0) ? "Wait until current player organ data finishes syncing." : "Every current player already has all body parts.")); return false; } private BodyRepairProof Proof(OrganManager manager, NetworkConnectionToClient connection, PlayerProfile profile, PlayerOrgans organs) { //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01b4: Invalid comparison between Unknown and I4 BodyRepairProof bodyRepairProof = new BodyRepairProof(); bodyRepairProof.CurrentConnection = LiveConnection(connection); bodyRepairProof.Authenticated = ((NetworkConnection)(connection?)).isAuthenticated ?? false; bodyRepairProof.Ready = ((NetworkConnection)(connection?)).isReady ?? false; bodyRepairProof.OwnedSpawnedCharacter = CurrentOwned(connection, profile, organs); bodyRepairProof.CurrentOrgans = bodyRepairProof.OwnedSpawnedCharacter && (Object)(object)organs != (Object)null && ((NetworkBehaviour)organs).isServer; bodyRepairProof.ActiveCharacter = (Object)(object)organs != (Object)null && ((Behaviour)organs).isActiveAndEnabled && ((Component)organs).gameObject.activeInHierarchy; if (!bodyRepairProof.OwnedSpawnedCharacter) { return bodyRepairProof; } PlayerController component = ((Component)((NetworkConnection)connection).identity).GetComponent<PlayerController>(); bodyRepairProof.NativeReferencesReady = NativeReferences(((NetworkConnection)connection).identity, profile, organs, component); bodyRepairProof.NativeDataBound = BoundData(manager, connection, profile, organs, out var steam, out var _); if (bodyRepairProof.NativeDataBound) { bodyRepairProof.LeftEye = steam.leftEye; bodyRepairProof.RightEye = steam.rightEye; bodyRepairProof.Body = steam.body; bodyRepairProof.Mouth = steam.mouth; } if ((Object)(object)component != (Object)null && busy.TryGetValue(component, out var value)) { bodyRepairProof.Sleeping = value.Sleeping; bodyRepairProof.Waking = !Finite(Time.time) || Time.time < value.WakeUntil; bodyRepairProof.MovementLocked = value.Locked; } if ((Object)(object)component != (Object)null && ((NetworkBehaviour)component).isLocalPlayer) { bodyRepairProof.Waking |= (bool)LocalWaking.GetValue(component); bodyRepairProof.MovementLocked |= component.IsLocked; } if ((Object)(object)component != (Object)null) { bodyRepairProof.MovementLocked |= (int)component.State == 2; } return bodyRepairProof; } private static bool NativeReferences(NetworkIdentity identity, PlayerProfile profile, PlayerOrgans organs, PlayerController controller) { if ((Object)(object)identity == (Object)null || (Object)(object)profile == (Object)null || (Object)(object)organs == (Object)null || (Object)(object)controller == (Object)null || (Object)(object)controller.head == (Object)null || !object.ReferenceEquals(OrganController.GetValue(organs), controller) || !object.ReferenceEquals(OrganProfile.GetValue(organs), profile) || !object.ReferenceEquals(OrganRigidbody.GetValue(organs), ((Component)identity).GetComponent<Rigidbody>()) || (Object)(object)((Component)identity).GetComponent<Rigidbody>() == (Object)null || !object.ReferenceEquals(OrganDrag.GetValue(organs), ((Component)identity).GetComponent<CustomDrag>()) || (Object)(object)((Component)identity).GetComponent<CustomDrag>() == (Object)null || (Object)/*isinst with value type is only supported in some contexts*/ == (Object)null || (Object)(object)((Component)identity).GetComponent<PlayerMouth>() == (Object)null) { return false; } object? value = LeftModel.GetValue(organs); GameObject model = (GameObject)((value is GameObject) ? value : null); object? value2 = RightModel.GetValue(organs); GameObject model2 = (GameObject)((value2 is GameObject) ? value2 : null); object? value3 = BodyModel.GetValue(organs); GameObject model3 = (GameObject)((value3 is GameObject) ? value3 : null); object? value4 = MouthModel.GetValue(organs); GameObject val = (GameObject)((value4 is GameObject) ? value4 : null); if (OwnModel(model, identity) && OwnModel(model2, identity) && OwnModel(model3, identity) && OwnModel(val, identity) && (Object)(object)val.GetComponent<MeshRenderer>() != (Object)null && ((Component)controller.head).transform.IsChildOf(((Component)identity).transform)) { if (((NetworkBehaviour)organs).isLocalPlayer) { object? value5 = OrganEyesUI.GetValue(organs); return (Object)((value5 is PlayerEyesUI) ? value5 : null) != (Object)null; } return true; } return false; } private static bool OwnModel(GameObject model, NetworkIdentity identity) { if ((Object)(object)model != (Object)null) { return model.transform.IsChildOf(((Component)identity).transform); } return false; } private static bool BoundData(OrganManager manager, NetworkConnectionToClient connection, PlayerProfile profile, PlayerOrgans organs, out PlayerOrganData steam, out PlayerOrganData current) { steam = null; current = null; Dictionary<ulong, PlayerOrganData> dictionary = (((Object)(object)manager == (Object)null) ? null : (SteamOrgans.GetValue(manager) as Dictionary<ulong, PlayerOrganData>)); if (dictionary != null && manager.OrganData != null && (Object)(object)profile != (Object)null && profile.steamId != 0 && connection != null && dictionary.TryGetValue(profile.steamId, out steam) && steam != null && manager.OrganData.TryGetValue(connection.connectionId, out current) && current != null && object.ReferenceEquals(steam.organsReference, organs) && object.ReferenceEquals(current.organsReference, organs) && steam.leftEye == current.leftEye && steam.rightEye == current.rightEye && steam.body == current.body) { return steam.mouth == current.mouth; } return false; } private static bool Whole(PlayerOrganData data) { if (data != null && data.leftEye && data.rightEye && data.body) { return data.mouth; } return false; } private static bool Finite(float number) { if (!float.IsNaN(number)) { return !float.IsInfinity(number); } return false; } private static bool CurrentOwned(NetworkConnectionToClient connection, PlayerProfile profile, PlayerOrgans organs) { if (LiveConnection(connection) && ((NetworkConnection)connection).isAuthenticated && ((NetworkConnection)connection).isReady && (Object)(object)((NetworkConnection)connection).identity != (Object)null && ((NetworkConnection)connection).identity.netId != 0 && ((NetworkConnection)connection).identity.isServer && (Object)(object)profile != (Object)null && profile.steamId != 0 && (Object)(object)organs != (Object)null && object.ReferenceEquals(((NetworkConnection)connection).identity.connectionToClient, connection) && NetworkServer.spawned.TryGetValue(((NetworkConnection)connection).identity.netId, out var value) && object.ReferenceEquals(value, ((NetworkConnection)connection).identity) && object.ReferenceEquals(((NetworkBehaviour)profile).netIdentity, ((NetworkConnection)connection).identity) && object.ReferenceEquals(((NetworkBehaviour)organs).netIdentity, ((NetworkConnection)connection).identity) && object.ReferenceEquals(((Component)((NetworkConnection)connection).identity).GetComponent<PlayerProfile>(), profile)) { return object.ReferenceEquals(((Component)((NetworkConnection)connection).identity).GetComponent<PlayerOrgans>(), organs); } return false; } private static bool LiveConnection(NetworkConnectionToClient connection) { if (connection != null && NetworkServer.connections.TryGetValue(connection.connectionId, out var value)) { return object.ReferenceEquals(value, connection); } return false; } private static bool ExactParty(List<NetworkConnectionToClient> party, NetworkConnectionToClient host) { if (party == null || party.Count < 1 || party.Count > 32 || party.Count != NetworkServer.connections.Count || !party.Contains(host)) { return false; } HashSet<int> hashSet = new HashSet<int>(); HashSet<ulong> hashSet2 = new HashSet<ulong>(); foreach (NetworkConnectionToClient item in party) { if (!LiveConnection(item) || (Object)(object)((NetworkConnection)item).identity == (Object)null || !hashSet.Add(item.connectionId)) { return false; } PlayerProfile component = ((Component)((NetworkConnection)item).identity).GetComponent<PlayerProfile>(); if (!CurrentOwned(item, component, ((Component)((NetworkConnection)item).identity).GetComponent<PlayerOrgans>()) || !hashSet2.Add(component.steamId)) { return false; } } return true; } private static bool NativeSceneReady(GameManager manager, List<NetworkConnectionToClient> party) { HashSet<int> hashSet = SceneReady.GetValue(manager) as HashSet<int>; if ((int)ExpectedPlayers.GetValue(manager) <= 0 || hashSet == null || hashSet.Count < party.Count) { return false; } foreach (NetworkConnectionToClient item in party) { if (!hashSet.Contains(item.connectionId)) { return false; } } return true; } internal void RegisterMachine(BodyShreddingMachine machine) { if (disposed || (Object)(object)machine == (Object)null) { return; } for (int num = machines.Count - 1; num >= 0; num--) { if ((Object)(object)machines[num] == (Object)null) { machines.RemoveAt(num); } } if (!machines.Contains(machine)) { if (machines.Count < 32) { machines.Add(machine); } else { trackingOverflow = true; } } } internal void NativeServerStopped() { machines.Clear(); busy.Clear(); trackingOverflow = false; } private bool RemovalBusy() { for (int num = machines.Count - 1; num >= 0; num--) { if ((Object)(object)machines[num] == (Object)null) { machines.RemoveAt(num); } else if (((NetworkBehaviour)machines[num]).isServer && ((Component)machines[num]).gameObject.activeInHierarchy && (bool)Processing.GetValue(machines[num])) { return true; } } return false; } internal void NativeLock(PlayerController controller, bool locked) { NativeBusy nativeBusy = BusyState(controller); if (nativeBusy != null) { nativeBusy.Locked = locked; } } internal void NativeBed(PlayerController controller) { NativeBusy nativeBusy = BusyState(controller); if (nativeBusy != null) { nativeBusy.Sleeping = true; nativeBusy.Locked = true; } } internal void NativeWake(PlayerController controller) { NativeBusy nativeBusy = BusyState(controller); if (nativeBusy != null) { nativeBusy.Sleeping = false; nativeBusy.Locked = false; nativeBusy.WakeUntil = (Finite(Time.time) ? (Time.time + 3.25f) : float.PositiveInfinity); } } private NativeBusy BusyState(PlayerController controller) { if (disposed || !NetworkServer.active || (Object)(object)controller == (Object)null) { return null; } if (busy.TryGetValue(controller, out var value)) { return value; } List<PlayerController> list = null; foreach (PlayerController key in busy.Keys) { if ((Object)(object)key == (Object)null) { if (list == null) { list = new List<PlayerController>(); } list.Add(key); } } if (list != null) { foreach (PlayerController item in list) { busy.Remove(item); } } if (busy.Count >= 64) { trackingOverflow = true; return null; } value = new NativeBusy(); busy.Add(controller, value); return value; } private void Prune(List<NetworkConnectionToClient> party) { HashSet<PlayerController> hashSet = new HashSet<PlayerController>(); foreach (NetworkConnectionToClient item in party) { PlayerController component = ((Component)((NetworkConnection)item).identity).GetComponent<PlayerController>(); if ((Object)(object)component != (Object)null) { hashSet.Add(component); } } List<PlayerController> list = null; foreach (PlayerController key in busy.Keys) { if ((Object)(object)key == (Object)null || !hashSet.Contains(key)) { if (list == null) { list = new List<PlayerController>(); } list.Add(key); } } if (list == null) { return; } foreach (PlayerController item2 in list) { busy.Remove(item2); } } private bool FeaturesEnabled() { if (disposed) { return false; } try { return canUse(); } catch (Exception error) { Report("body repair guard", error); return false; } } private void Write(string message) { if (log != null) { try { log(message); } catch { } } } private void Report(string operation, Exception error) { if (errors.Count < 8 && errors.Add(operation)) { Write("Native " + operation + " held: " + error.GetBaseException().Message); } } private static bool NativeApiAvailable() { if (ManagerInstance != null && ManagerInstance.IsStatic && OrganInstance != null && OrganInstance.IsStatic && Transitioning != null && SceneReady != null && ExpectedPlayers != null && SteamOrgans != null && LeftModel != null && RightModel != null && BodyModel != null && MouthModel != null && OrganController != null && OrganProfile != null && OrganRigidbody != null && OrganDrag != null && OrganSettings != null && OrganEyesUI != null && LocalWaking != null && Processing != null && AccessTools.Method(typeof(OrganManager), "SetOrganDataBySteamId", new Type[5] { typeof(ulong), typeof(bool), typeof(bool), typeof(bool), typeof(bool) }, (Type[])null) != null && AccessTools.Method(typeof(PlayerController), "ServerLock", new Type[1] { typeof(bool) }, (Type[])null) != null && AccessTools.Method(typeof(PlayerController), "ServerWakeUp", Type.EmptyTypes, (Type[])null) != null && AccessTools.Method(typeof(SpawnBoxPlayerRagdollTrigger), "AssignPlayer", new Type[1] { typeof(PlayerController) }, (Type[])null) != null && AccessTools.Method(typeof(BodyShreddingMachine), "OnStartClient", Type.EmptyTypes, (Type[])null) != null) { return AccessTools.Method(typeof(CustomNetworkManager), "OnStopServer", Type.EmptyTypes, (Type[])null) != null; } return false; } public static string SelfTest() { if (!NativeApiAvailable()) { throw new InvalidOperationException("Native body repair API shape changed."); } BodyRepairProof bodyRepairProof = new BodyRepairProof(); bodyRepairProof.CurrentConnection = true; bodyRepairProof.Authenticated = true; bodyRepairProof.Ready = true; bodyRepairProof.OwnedSpawnedCharacter = true; bodyRepairProof.CurrentOrgans = true; bodyRepairProof.ActiveCharacter = true; bodyRepairProof.NativeReferencesReady = true; bodyRepairProof.NativeDataBound = true; bodyRepairProof.LeftEye = true; bodyRepairProof.RightEye = true; bodyRepairProof.Mouth = true; bodyRepairProof.Body = false; BodyRepairProof proof = bodyRepairProof; if (BodyRepairPolicy.Classify(proof) != BodyRepairDisposition.Eligible || BodyRepairPolicy.CanStart(enabled: true, server: false, actualHost: true, validPhase: true, partyReady: true, sceneReady: true, resetting: false, shredderProcessing: false)) { throw new InvalidOperationException("Body repair policy failed."); } return "PASS: native scoped body repair API and host/missing-body policy (offline)"; } public void Dispose() { if (!disposed) { disposed = true; machines.Clear(); busy.Clear(); if (object.ReferenceEquals(Active, this)) { Active = null; } } } } [HarmonyPatch(typeof(BodyShreddingMachine), "OnStartClient")] internal static class BodyRepairMachinePatch { private static void Postfix(BodyShreddingMachine __instance) { if (ChaosBodyRepair.Active != null) { ChaosBodyRepair.Active.RegisterMachine(__instance); } } } [HarmonyPatch(typeof(PlayerController), "ServerLock")] internal static class BodyRepairLockPatch { private static void Postfix(PlayerController __instance, bool __0) { if (ChaosBodyRepair.Active != null) { ChaosBodyRepair.Active.NativeLock(__instance, __0); } } } [HarmonyPatch(typeof(SpawnBoxPlayerRagdollTrigger), "AssignPlayer")] internal static class BodyRepairBedPatch { private static void Prefix(PlayerController __0) { if (ChaosBodyRepair.Active != null) { ChaosBodyRepair.Active.NativeBed(__0); } } } [HarmonyPatch(typeof(PlayerController), "ServerWakeUp")] internal static class BodyRepairWakePatch { private static void Postfix(PlayerController __instance) { if (ChaosBodyRepair.Active != null) { ChaosBodyRepair.Active.NativeWake(__instance); } } } [HarmonyPatch(typeof(CustomNetworkManager), "OnStopServer")] internal static class BodyRepairServerStopPatch { private static void Postfix() { if (ChaosBodyRepair.Active != null) { ChaosBodyRepair.Active.NativeServerStopped(); } } } } namespace ChaosNight.Core { [Serializable] public sealed class Rules { public bool Enabled = true; public int Intensity = 4; public double JackpotPercent = 3.0; public bool ContractsEnabled = true; public bool ClosingTimeEnabled = true; public double MaxBonusQuotaFraction = 0.35; public int Seed; public string DisabledModifiers = ""; public bool PurchasesSupported; public int MaxTicketsPerDay = 3; public int ChapterRounds = 4; public bool NegativesEnabled = true; public double NegativeDayPercent = 50.0; public bool TimedNegativesEnabled; public int PenaltySeverity = 2; } public enum RewardKind { Cash, Tickets, QuotaFactor, ClosingTimeFactor, Notice, PenaltyCash } [Serializable] public sealed class Reward { public string Id; public RewardKind Kind; public double Amount; public string Description; public ulong Player; public int Day; public int Round; public double CashMantissa; public long CashExponent; public bool CashBound; public ulong Actor; public string ActorName; public bool TaskCompleted; public bool NpcKill; public double ReservedAmount; } [Serializable] public sealed class NegativeProgress { public string Id; public string Name; public string Description; public string PairedModifierId; public string RiskBasis; public string PenaltyId; public int Required; public int Count; public double ProfitTarget; public double Profit; public double BaseRiskRatio; public double RiskRatio; public double DeadlineSeconds; public double RemainingSeconds; public bool Timed; public bool Completed; public bool Failed; public bool WaivedCompletion; public ulong Completer; public ulong[] Roster = new ulong[0]; public List<ulong> Participants = new List<ulong>(); public List<int> Games = new List<int>(); } [Serializable] public sealed class ResultInput { public long Sequence; public ulong Player; public int Game; public double BetRatio; public double PayoutRatio; public double BalanceRatio; public int EffectsMask = -1; public int FeaturedGameAtStart = -1; } [Serializable] public sealed class Choice { public string Id; public string Name; public string Description; public string Kind; } [Serializable] public sealed class PlayerProgress { public ulong Id; public int Plays; public int Wins; public double Profit; } [Serializable] public sealed class GameProgress { public int Id; public int Plays; public int Wins; public double Profit; } [Serializable] public sealed class Cooldown { public string Id; public int Round; } [Serializable] public sealed class RunState { public int Version = 1; public string RunId; public uint RandomState; public Rules ActiveRules; public int Day = -1; public int Floor = -1; public int Round; public int Chapter = 1; public bool DayOpen; public bool DayEnded; public bool DayEnabled; public bool BetItAllUsed; public int BetItAllConsumedDay = -1; public ulong[] Players = new ulong[0]; public int[] AvailableGames = new int[0]; public List<PlayerProgress> PlayerProgress = new List<PlayerProgress>(); public List<GameProgress> GameProgress = new List<GameProgress>(); public List<string> ModifierIds = new List<string>(); public List<string> ModifierNames = new List<string>(); public List<string> ModifierDescriptions = new List<string>(); public List<string> ModifierDeck = new List<string>(); public List<string> ContractDeck = new List<string>(); public List<Cooldown> ModifierCooldowns = new List<Cooldown>(); public List<string> PerkIds = new List<string>(); public List<string> PerkNames = new List<string>(); public List<string> PerkDescriptions = new List<string>(); public List<Choice> Choices = new List<Choice>(); public int ChoiceRound; public int PendingPerkDrafts; public string ContractId = ""; public string ContractName = ""; public string ContractDescription = ""; public string ContractProgress = ""; public double ContractTarget; public bool ContractCompleted; public int ContractFeaturedGame; public double QuotaFactor = 1.0; public double ClosingTimeFactor = 1.0; public double CashAwarded; public int DeliveryVersion; public bool LegacyCash; public double CashDelivered; public List<Reward> PendingCashRewards = new List<Reward>(); public List<Reward> CashReceipts = new List<Reward>(); public int NegativeVersion; public uint NegativeRandomState; public int NegativeScheduleVersion; public uint NegativeDayRandomState; public bool NegativeDayChosen; public double NegativeDaySeconds; public double NegativeElapsedSeconds; public bool NegativesResolved; public List<NegativeProgress> Negatives = new List<NegativeProgress>(); public List<string> NegativeDeck = new List<string>(); public List<Cooldown> NegativeCooldowns = new List<Cooldown>(); public double PenaltyReserved; public double PenaltyDelivered; public List<Reward> PendingPenaltyRewards = new List<Reward>(); public List<Reward> PenaltyReceipts = new List<Reward>(); public List<string> KilledNpcKeys = new List<string>(); public double NpcCashReserved; public double NpcCashDelivered; public int TicketsAwarded; public double NativeProfit; public double NativeLosses; public double PeakBalance; public double LowBalance; public double LastBalance; public bool BalanceKnown; public double Purchases; public int QualifyingPlays; public int SmallStakePlays; public double SmallStakeProfit; public int HighRollerPlays; public int WinningStreak; public int BestDayStreak; public bool HadSetback; public bool ComebackCompleted; public bool RelayCompleted; public bool VarietyCompleted; public bool StreakCompleted; public bool RiskDividendCompleted; public bool SafetyFundUsed; public bool RescueUsed; public int FeaturedGame; public int RotationPlays; public bool JackpotChecked; public bool GoldenPayoutPending; public int LastJackpotRound = -100; public int WildcardChapter = -1; public int SecondOpinionChapter = -1; public int SecondDrawChapter = -1; public int HouseCreditChapter = -1; public long LastSequence; public long AwardSequence; public List<string> RewardIds = new List<string>(); public List<string> AppliedRewardIds = new List<string>(); public List<string> DiscoveredModifiers = new List<string>(); public List<string> DiscoveredJackpots = new List<string>(); public List<string> CompletedContractIds = new List<string>(); public List<string> Medals = new List<string>(); public List<string> RecentEvents = new List<string>(); public int DaysSurvived; public int ContractsCompleted; public int JackpotsFound; public int BestRunStreak; public double BiggestRecoveryRatio; public string NextModifierPreview = ""; } public sealed class Definition { public readonly string Id; public readonly string Name; public readonly string Description; public Definition(string id, string name, string description) { Id = id; Name = name; Description = description; } } public sealed class Engine { public const double MinimumStake = 0.001; public const int GoldenPayoutBit = 1073741824; public const int MaximumPendingCashRewards = 256; public const int MaximumCashReceipts = 64; public const int MaximumPenaltyReceipts = 64; public const int MaximumNpcKillsPerDay = 512; public RunState State; private Rules pendingRules; private ulong resultActor; public static readonly Definition[] Modifiers = new Definition[17] { new Definition("house_match", "House Match", "Earn 20% extra on casino profit. Today's cash target is 10% higher."), new Definition("closing_time", "Closing Time", "20% less time, 20% less cash to earn."), new Definition("pressure_cooker", "Pressure Cooker", "Beat today's higher cash target for 1 ticket."), new Definition("breather", "Breather", "An easier day: 15% less cash to earn."), new Definition("hot_table", "Hot Table", "Win at the featured game for 25% extra profit."), new Definition("variety_show", "Variety Show", "Win on 3 different game types, or all available types if fewer."), new Definition("crew_relay", "Crew Relay", "Everyone finishes 1 casino game. Playing solo? Finish 3."), new Definition("risk_dividend", "Risk Dividend", "Finish 3 larger bets for 1 ticket. The menu shows the minimum bet."), new Definition("lucky_rotation", "Lucky Rotation", "Win at the featured game for 20% extra profit. It changes after 4 finished games."), new Definition("rebate_night", "Rebate Night", "Get 10% of casino losses back."), new Definition("comeback_night", "Comeback Night", "After shared cash drops, win it back for a bonus. The menu shows the amount."), new Definition("streak_night", "Streak Night", "Win 3 crew games in a row for a cash bonus."), new Definition("ticket_rush", "Ticket Rush", "Meet today's cash target for 2 tickets."), new Definition("frugal_friends", "Frugal Friends", "Meet today's target without overspending in the shop. The menu shows your ticket budget."), new Definition("plain_luck", "Plain Luck", "Perks paused today. Meet the cash target for a new perk choice."), new Definition("double_trouble", "Double Trouble", "An extra modifier today. Meet the cash target for 1 extra ticket."), new Definition("bet_it_all", "BET IT ALL", "Bet all shared cash on one casino game. One use for the crew today; refunds don't restore it.") }; public static readonly Definition[] Contracts = new Definition[12] { new Definition("casino_tour", "Casino Tour", "Play 3 different game types, or all available types if fewer."), new Definition("everybody_in", "Everybody In", "Everyone finishes 1 casino game. Playing solo? Finish 3."), new Definition("profit_ladder", "Profit Ladder", "Make today's casino profit goal after losses."), new Definition("recovery_crew", "Recovery Crew", "After shared cash drops, win it back."), new Definition("budget_night", "Budget Night", "Meet today's target and stay within the shop ticket budget."), new Definition("small_stakes", "Small Stakes", "Make casino profit with small bets. The menu shows the bet limit."), new Definition("high_roller", "High Roller", "Finish 3 larger bets. The menu shows the minimum bet."), new Definition("team_streak", "Team Streak", "Win 3 crew games in a row."), new Definition("clean_run", "Clean Run", "Meet today's target without rebates, Safety Fund, House Refund or Debt Relief."), new Definition("featured_game", "Featured Game", "Make today's profit goal at the featured game."), new Definition("strong_finish", "Strong Finish", "Beat a bigger casino profit goal after losses."), new Definition("balanced_crew", "Balanced Crew", "2 different players each win a game. Playing solo? Win 2.") }; public static readonly Definition[] Perks = new Definition[8] { new Definition("scout", "Scout", "See the next day's modifier."), new Definition("wildcard", "Wildcard", "Reroll today's modifiers before betting. Once per chapter; Closing Time stays locked."), new Definition("second_opinion", "Second Opinion", "Reroll today's bonus goal before betting. Once per chapter."), new Definition("house_credit", "House Credit", "Your first completed goal each chapter gets an extra cash bonus."), new Definition("safety_fund", "Safety Fund", "Get 15% of the crew's first casino loss back each day."), new Definition("team_dividend", "Team Dividend", "Bonus goals pay 25% more cash."), new Definition("second_draw", "Second Draw", "Reroll an open perk choice once per chapter."), new Definition("negotiator", "Negotiator", "Choose between 2 bonus goals before betting at each chapter start.") }; public static readonly Definition[] Jackpots = new Definition[5] { new Definition("vault", "The Vault Opens", "Choose cash, up to 3 tickets, or a new perk."), new Definition("golden_payout", "Golden Payout", "The crew's next new winning bet gets matching profit, within today's bonus cap."), new Definition("house_refund", "House Refund", "Get half of today's casino losses back."), new Definition("lucky_shipment", "Lucky Shipment", "Choose another crew perk."), new Definition("debt_relief", "Debt Relief", "Less cash to earn today. The menu shows the new target.") }; public static readonly Definition[] NegativeModifiers = new Definition[8] { new Definition("participation", "Everybody Plays", "Everyone finishes 1 game before day end. Playing solo? Finish 3."), new Definition("variety", "Change Tables", "Play 2 different game types before day end."), new Definition("net_profit", "Earn Your Keep", "Make the casino profit goal before day end. The menu shows the amount."), new Definition("six_plays", "Keep Playing", "Finish 6 casino games as a crew before day end."), new Definition("timed_participation", "Early Participation", "Everyone finishes 1 game before the timer runs out. Playing solo? Finish 3."), new Definition("timed_variety", "Early Table Tour", "Play 2 different game types before the timer runs out."), new Definition("timed_net_profit", "Early Profit", "Make the casino profit goal before the timer runs out."), new Definition("timed_six_plays", "Early Six", "Finish 6 casino games as a crew before the timer runs out.") }; public Engine(Rules rules) { pendingRules = Normalize(rules); State = new RunState { DeliveryVersion = 1, NegativeVersion = 1, NegativeScheduleVersion = 1 }; } public void SetRules(Rules rules) { pendingRules = Normalize(rules); } public void Restore(RunState saved) { if (saved == null || saved.Version != 1) { throw new ArgumentException("Unsupported Chaos checkpoint."); } if (saved.Round < 0 || saved.LastSequence < 0 || saved.AwardSequence < 0 || !Finite(saved.CashAwarded) || saved.CashAwarded < 0.0 || !Finite(saved.QuotaFactor) || saved.QuotaFactor < 0.65 || saved.QuotaFactor > 1.5) { throw new ArgumentException("Invalid Chaos checkpoint."); } double[] array = new double[9] { saved.NativeProfit, saved.NativeLosses, saved.PeakBalance, saved.LowBalance, saved.LastBalance, saved.Purchases, saved.SmallStakeProfit, saved.ContractTarget, saved.BiggestRecoveryRatio }; foreach (double n in array) { if (!Finite(n)) { throw new ArgumentException("Non-finite Chaos checkpoint value."); } } if (saved.NativeLosses < 0.0 || saved.Purchases < 0.0 || saved.TicketsAwarded < 0 || saved.TicketsAwarded > 10 || saved.QualifyingPlays < 0 || saved.HighRollerPlays < 0 || saved.ClosingTimeFactor < 0.8 || saved.ClosingTimeFactor > 1.0) { throw new ArgumentException("Invalid Chaos checkpoint counters."); } if (saved.BetItAllConsumedDay < -1) { throw new ArgumentException("Invalid BET IT ALL consumption day."); } if (saved.BetItAllUsed && saved.BetItAllConsumedDay < 0 && saved.Day >= 0) { saved.BetItAllConsumedDay = saved.Day; } State = saved; State.BetItAllUsed = State.Day >= 0 && State.BetItAllConsumedDay == State.Day; RepairLists(); if (State.DeliveryVersion < 0 || State.DeliveryVersion > 1) { throw new ArgumentException("Unsupported cash-delivery checkpoint."); } if (State.DeliveryVersion == 0) { if (State.PendingCashRewards.Count != 0 || State.CashReceipts.Count != 0) { throw new ArgumentException("Unversioned cash-delivery ledger."); } State.CashDelivered = State.CashAwarded; State.LegacyCash = State.CashAwarded > 0.0; State.DeliveryVersion = 1; } ValidateCashLedger(); if (State.ModifierIds.Count > 2 || State.PerkIds.Count > 3 || State.Choices.Count > 3 || !KnownIds(State.ModifierIds, Modifiers) || !KnownIds(State.PerkIds, Perks) || (State.ContractId.Length > 0 && Find(Contracts, State.ContractId) == null)) { throw new ArgumentException("Unknown or incompatible Chaos checkpoint content."); } foreach (Choice choice in State.Choices) { if (choice == null || (choice.Kind != "perk" && choice.Kind != "contract" && choice.Kind != "vault") || (choice.Kind == "perk" && Find(Perks, choice.Id) == null) || (choice.Kind == "contract" && Find(Contracts, choice.Id) == null) || (choice.Kind == "vault" && choice.Id != "vault_cash" && choice.Id != "vault_tickets" && choice.Id != "vault_perk")) { throw new ArgumentException("Invalid Chaos checkpoint choice."); } } State.ActiveRules = Normalize(State.ActiveRules ?? pendingRules); if (State.NegativeScheduleVersion == 0) { State.NegativeDayChosen = State.Negatives.Count > 0; State.NegativeScheduleVersion = 1; } ValidateNegatives(); if (State.Round == 0 && string.IsNullOrEmpty(State.RunId)) { State.RandomState = 0u; } else if (State.RandomState == 0) { State.RandomState = 1831565813u; } SyncDescriptions(); UpdateProgress(); } public List<Reward> BeginDay(int day, int floor, ulong[] players, int[] gameTypes, int worldSeed, double originalDuration = 300.0) { resultActor = 0uL; List<Reward> list = new List<Reward>(); if (day < 0 || floor < 0) { return list; } if (State.Day == day && State.Floor == floor && State.Round > 0) { return list; } if (State.DayOpen) { list.AddRange(EndDay(quotaMet: false)); } CancelPendingPenalties(); list.RemoveAll((Reward r) => r.Kind == RewardKind.PenaltyCash); State.ActiveRules = Normalize(pendingRules); if (State.RandomState == 0) { State.RandomState = (uint)(((State.ActiveRules.Seed == 0) ? worldSeed : State.ActiveRules.Seed) ^ -1640531527); if (State.RandomState == 0) { State.RandomState = 1831565813u; } State.RunId = State.RandomState.ToString("x8", CultureInfo.InvariantCulture); } State.Day = day; State.Floor = floor; State.Round++; State.Chapter = 1 + (State.Round - 1) / State.ActiveRules.ChapterRounds; State.DayOpen = true; State.DayEnded = false; State.DayEnabled = State.ActiveRules.Enabled; State.BetItAllUsed = State.BetItAllConsumedDay == day; State.Players = Unique(players); State.AvailableGames = Unique(gameTypes); State.PlayerProgress.Clear(); State.GameProgress.Clear(); ulong[] players2 = State.Players; foreach (ulong id in players2) { State.PlayerProgress.Add(new PlayerProgress { Id = id }); } int[] availableGames = State.AvailableGames; foreach (int id2 in availableGames) { State.GameProgress.Add(new GameProgress { Id = id2 }); } State.ModifierIds.Clear(); State.ModifierNames.Clear(); State.ModifierDescriptions.Clear(); State.ContractId = ""; State.ContractName = ""; State.ContractDescription = ""; State.ContractProgress = ""; State.ContractCompleted = false; State.ContractTarget = 0.3 + 0.05 * (double)Math.Min(4, State.Chapter - 1); State.QuotaFactor = 1.0; State.ClosingTimeFactor = 1.0; State.NegativeVersion = 1; State.Negatives.Clear(); State.NegativesResolved = false; State.NegativeScheduleVersion = 1; State.NegativeDayChosen = false; State.KilledNpcKeys.Clear(); State.NpcCashReserved = 0.0; State.NpcCashDelivered = 0.0; State.NegativeElapsedSeconds = 0.0; State.PenaltyReserved = 0.0; State.PenaltyDelivered = 0.0; State.NegativeDaySeconds = ((Finite(originalDuration) && originalDuration > 0.0) ? Math.Min(86400.0, originalDuration) : 300.0); State.CashAwarded = 0.0; State.CashDelivered = 0.0; State.LegacyCash = false; State.TicketsAwarded = 0; State.NativeProfit = 0.0; State.NativeLosses = 0.0; State.Purchases = 0.0; State.QualifyingPlays = 0; State.SmallStakePlays = 0; State.SmallStakeProfit = 0.0; State.HighRollerPlays = 0; State.WinningStreak = 0; State.BestDayStreak = 0; State.BalanceKnown = false; State.HadSetback = false; State.ComebackCompleted = false; State.RelayCompleted = false; State.VarietyCompleted = false; State.StreakCompleted = false; State.RiskDividendCompleted = false; State.SafetyFundUsed = false; State.RescueUsed = false; State.JackpotChecked = false; State.GoldenPayoutPending = false; State.RotationPlays = 0; if (State.Choices.Count > 0 && State.Choices[0].Kind != "perk") { State.Choices.Clear(); } State.FeaturedGame = ((State.AvailableGames.Length == 0) ? (-1) : State.AvailableGames[Next(State.AvailableGames.Length)]); if (!State.DayEnabled) { SyncDescriptions(); return list; } SelectModifiers(); ApplyFactors(list); if (State.ActiveRules.ContractsEnabled && State.AvailableGames.Length > 0) { if (PerkActive("negotiator") && (State.Round - 1) % State.ActiveRules.ChapterRounds == 0 && State.Choices.Count == 0) { OfferContractChoices(); } else { SetContract(DrawContract()); } } ChooseNegativeDay(); SelectNegatives(list); SyncDescriptions(); UpdateProgress(); Issue(list, RewardKind.Notice, 0.0, "Chaos round " + State.Round + ": " + string.Join(" + ", State.ModifierNames.ToArray()), 0uL); RefreshPreview(); return list; } public List<Reward> Settle(ResultInput input) { resultActor = 0uL; List<Reward> list = new List<Reward>(); if (input == null || !State.DayOpen || !State.DayEnabled || input.Sequence <= State.LastSequence) { return list; } State.LastSequence = input.Sequence; if (input.Sequence <= 0 || !Finite(input.BetRatio) || !Finite(input.PayoutRatio) || !Finite(input.BalanceRatio) || input.BetRatio < 0.001 || input.PayoutRatio < 0.0 || input.BalanceRatio < 0.0 || input.BetRatio > 1E+100 || input.PayoutRatio > 1E+100 || !Contains(State.Av