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 allowed; private readonly Func available; private readonly Func consume; private readonly Func authentic; private readonly Action log; private readonly HashSet outstanding = new HashSet(); private readonly Dictionary labels = new Dictionary(); private readonly List scratch = new List(); 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 featureAllowed, Func permissionAvailable, Func consumePermission, Func authenticPlayer, Action 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(), 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 { 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) && 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 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 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 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 canUse; private readonly Action 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 canUseNativeFeatures, Action 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.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(), 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()) || !BedWakePolicy.SameOwner(val2, ((Component)controller).GetComponent())) { 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.Enter(__instance); } private static Exception Finalizer(Exception __exception, PlayerController __state) { BedWakeCallScope.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 canRefresh; private readonly Action log; private readonly Dictionary keypads = new Dictionary(); private readonly List scratch = new List(); 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 refreshAllowed, Action 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 Targets = new List(); public int Whole; public int Busy; public int Incomplete; } internal static ChaosBodyRepair Active; private readonly Func canUse; private readonly Func> readyParty; private readonly Action log; private readonly List machines = new List(); private readonly Dictionary busy = new Dictionary(); private readonly HashSet errors = new HashSet(); 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 nativeFeaturesAllowed, Func> readyNativeParty, Action 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 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(); PlayerOrgans component2 = ((Component)((NetworkConnection)item).identity).GetComponent(); 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(); 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()) || (Object)(object)((Component)identity).GetComponent() == (Object)null || !object.ReferenceEquals(OrganDrag.GetValue(organs), ((Component)identity).GetComponent()) || (Object)(object)((Component)identity).GetComponent() == (Object)null || (Object)/*isinst with value type is only supported in some contexts*/ == (Object)null || (Object)(object)((Component)identity).GetComponent() == (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() != (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 dictionary = (((Object)(object)manager == (Object)null) ? null : (SteamOrgans.GetValue(manager) as Dictionary)); 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(), profile)) { return object.ReferenceEquals(((Component)((NetworkConnection)connection).identity).GetComponent(), 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 party, NetworkConnectionToClient host) { if (party == null || party.Count < 1 || party.Count > 32 || party.Count != NetworkServer.connections.Count || !party.Contains(host)) { return false; } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); 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(); if (!CurrentOwned(item, component, ((Component)((NetworkConnection)item).identity).GetComponent()) || !hashSet2.Add(component.steamId)) { return false; } } return true; } private static bool NativeSceneReady(GameManager manager, List party) { HashSet hashSet = SceneReady.GetValue(manager) as HashSet; 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 list = null; foreach (PlayerController key in busy.Keys) { if ((Object)(object)key == (Object)null) { if (list == null) { list = new List(); } 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 party) { HashSet hashSet = new HashSet(); foreach (NetworkConnectionToClient item in party) { PlayerController component = ((Component)((NetworkConnection)item).identity).GetComponent(); if ((Object)(object)component != (Object)null) { hashSet.Add(component); } } List list = null; foreach (PlayerController key in busy.Keys) { if ((Object)(object)key == (Object)null || !hashSet.Contains(key)) { if (list == null) { list = new List(); } 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 Participants = new List(); public List Games = new List(); } [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 = new List(); public List GameProgress = new List(); public List ModifierIds = new List(); public List ModifierNames = new List(); public List ModifierDescriptions = new List(); public List ModifierDeck = new List(); public List ContractDeck = new List(); public List ModifierCooldowns = new List(); public List PerkIds = new List(); public List PerkNames = new List(); public List PerkDescriptions = new List(); public List Choices = new List(); 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 PendingCashRewards = new List(); public List CashReceipts = new List(); 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 Negatives = new List(); public List NegativeDeck = new List(); public List NegativeCooldowns = new List(); public double PenaltyReserved; public double PenaltyDelivered; public List PendingPenaltyRewards = new List(); public List PenaltyReceipts = new List(); public List KilledNpcKeys = new List(); 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 RewardIds = new List(); public List AppliedRewardIds = new List(); public List DiscoveredModifiers = new List(); public List DiscoveredJackpots = new List(); public List CompletedContractIds = new List(); public List Medals = new List(); public List RecentEvents = new List(); 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 BeginDay(int day, int floor, ulong[] players, int[] gameTypes, int worldSeed, double originalDuration = 300.0) { resultActor = 0uL; List list = new List(); 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 Settle(ResultInput input) { resultActor = 0uL; List list = new List(); 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.AvailableGames, input.Game) || !Contains(State.Players, input.Player)) { return list; } resultActor = input.Player; int num = ((input.EffectsMask == -1) ? GetEffectsMask() : input.EffectsMask); int num2 = ((input.FeaturedGameAtStart < 0) ? State.FeaturedGame : input.FeaturedGameAtStart); if (State.Choices.Count > 0 && State.Choices[0].Kind == "contract") { string id = State.Choices[0].Id; State.Choices.Clear(); SetContract(id); } double num3 = input.PayoutRatio - input.BetRatio; double num4 = Math.Max(0.0, num3); double num5 = Math.Max(0.0, 0.0 - num3); State.QualifyingPlays++; State.NativeProfit = Math.Min(1E+100, State.NativeProfit + num3); State.NativeLosses = Math.Min(1E+100, State.NativeLosses + num5); PlayerProgress playerProgress = FindPlayer(input.Player); GameProgress gameProgress = FindGame(input.Game); playerProgress.Plays++; gameProgress.Plays++; playerProgress.Profit += num3; gameProgress.Profit += num3; if (num4 > 0.0) { playerProgress.Wins++; gameProgress.Wins++; State.WinningStreak++; } else { State.WinningStreak = 0; } State.BestDayStreak = Math.Max(State.BestDayStreak, State.WinningStreak); State.BestRunStreak = Math.Max(State.BestRunStreak, State.WinningStreak); if (input.BetRatio <= 0.01) { State.SmallStakePlays++; State.SmallStakeProfit += num3; } if (input.BetRatio >= 0.03) { State.HighRollerPlays++; } TrackBalance(input.BalanceRatio, num3); if (Effect("house_match", num)) { Cash(list, num4 * 0.2, "House Match", input.Player); } if (Effect("hot_table", num) && input.Game == num2) { Cash(list, num4 * 0.25, "Hot Table", input.Player); } if (Effect("lucky_rotation", num)) { if (input.Game == num2) { Cash(list, num4 * 0.2, "Lucky Rotation", input.Player); } State.RotationPlays++; if (State.RotationPlays % 4 == 0 && State.AvailableGames.Length > 1) { int num6 = Array.IndexOf(State.AvailableGames, State.FeaturedGame); State.FeaturedGame = State.AvailableGames[(num6 + 1) % State.AvailableGames.Length]; Issue(list, RewardKind.Notice, 0.0, "Lucky Rotation now features game " + State.FeaturedGame + ".", 0uL); } } if (Effect("rebate_night", num) && num5 > 0.0) { State.RescueUsed = true; Cash(list, num5 * 0.1, "Rebate Night", input.Player); } if (PerkActive("safety_fund") && !State.SafetyFundUsed && num5 > 0.0) { State.SafetyFundUsed = true; State.RescueUsed = true; Cash(list, num5 * 0.15, "Safety Fund", input.Player); } if (Effect("variety_show", num) && !State.VarietyCompleted && GameCount(wins: true) >= Math.Min(3, State.AvailableGames.Length)) { State.VarietyCompleted = true; Cash(list, 0.06, "Variety Show complete", 0uL); } if (Effect("crew_relay", num) && !State.RelayCompleted && CrewReady(wins: false)) { State.RelayCompleted = true; Cash(list, 0.05, "Crew Relay complete", 0uL); } if (Effect("risk_dividend", num) && !State.RiskDividendCompleted && State.HighRollerPlays >= 3) { State.RiskDividendCompleted = true; Tickets(list, 1, "Risk Dividend complete"); } if (Effect("comeback_night", num) && State.ComebackCompleted) { CashOnce(list, "comeback", 0.06, "Comeback Night complete"); } if (Effect("streak_night", num) && !State.StreakCompleted && State.WinningStreak >= 3) { State.StreakCompleted = true; Cash(list, 0.05, "Streak Night complete", 0uL); } if (State.GoldenPayoutPending && (num & 0x40000000) != 0 && num4 > 0.0) { State.GoldenPayoutPending = false; Cash(list, Math.Min(0.2, num4), "Golden Payout", input.Player); } if (ContractSatisfied(quotaMet: false)) { CompleteContract(list); } AdvanceNegatives(input, num3, list); CheckJackpot(list); UpdateProgress(); if (State.BestRunStreak >= 5) { Discover(State.Medals, "Five-win streak"); } if (GameCount(wins: true) >= 3) { Discover(State.Medals, "Casino explorer"); } resultActor = 0uL; return list; } public void RecordPurchase(double quotaRatio) { if (State.DayOpen && State.DayEnabled && State.ActiveRules.PurchasesSupported && Finite(quotaRatio) && quotaRatio > 0.0) { State.Purchases = Math.Min(1E+100, State.Purchases + quotaRatio); } UpdateProgress(); } public void WaivePlayer(ulong id) { if (!State.DayOpen || State.Players.Length <= 1) { return; } List list = new List(State.Players); list.Remove(id); State.Players = list.ToArray(); foreach (NegativeProgress negative in State.Negatives) { string text = (negative.Timed ? negative.Id.Substring(6) : negative.Id); if (text != "participation" || negative.Completed || negative.Failed || !Contains(negative.Roster, id) || negative.Participants.Contains(id)) { continue; } List list2 = new List(negative.Roster); list2.Remove(id); if (list2.Count != 0) { negative.Roster = list2.ToArray(); negative.Required = ((list2.Count == 1) ? 3 : list2.Count); negative.Count = ((list2.Count == 1) ? Math.Min(3, FindPlayer(list2[0]).Plays) : negative.Participants.Count); if (negative.Count >= negative.Required) { negative.Completed = true; negative.Completer = 0uL; negative.WaivedCompletion = true; } } } UpdateProgress(); } public List EndDay(bool quotaMet) { resultActor = 0uL; List list = new List(); if (!State.DayOpen || State.DayEnded) { return list; } list.AddRange(ResolveNegativesAtDayEnd()); if (State.DayEnabled) { if (ContractSatisfied(quotaMet)) { CompleteContract(list); } if (quotaMet) { State.DaysSurvived++; if (Active("pressure_cooker")) { Tickets(list, 1, "Pressure Cooker complete"); } if (Active("ticket_rush")) { Tickets(list, 2, "Ticket Rush complete"); } if (Active("double_trouble")) { Tickets(list, 1, "Double Trouble complete"); } if (Active("frugal_friends") && State.Purchases <= 0.35) { Cash(list, 0.05, "Frugal Friends complete", 0uL); } if (Active("plain_luck")) { State.PendingPerkDrafts++; } if (State.Round % State.ActiveRules.ChapterRounds == 0) { State.PendingPerkDrafts++; } if (State.ComebackCompleted) { Discover(State.Medals, "Recovery crew"); } if (State.CashAwarded == 0.0 && !State.RescueUsed) { Discover(State.Medals, "Self sufficient"); } } if (State.Choices.Count > 0 && State.Choices[0].Kind != "perk") { State.Choices.Clear(); Issue(list, RewardKind.Notice, 0.0, "Unclaimed round choices expired.", 0uL); } if (State.GoldenPayoutPending) { Issue(list, RewardKind.Notice, 0.0, "Unused Golden Payout expired at the round boundary.", 0uL); } State.GoldenPayoutPending = false; MaybeOfferPerks(); Issue(list, RewardKind.Notice, 0.0, "Round recap: " + State.QualifyingPlays + " wagers; " + Percent(State.NativeProfit) + " native profit; " + (State.ContractCompleted ? "contract completed" : "contract incomplete") + ".", 0uL); } State.DayOpen = false; State.DayEnded = true; RefreshPreview(); return list; } public List Choose(int optionIndex) { resultActor = 0uL; List list = new List(); if (optionIndex < 0 || optionIndex >= State.Choices.Count) { return list; } Choice choice = State.Choices[optionIndex]; if (choice.Kind != "perk" && (!State.DayOpen || State.ChoiceRound != State.Round)) { return list; } if (choice.Kind == "contract" && State.QualifyingPlays > 0) { return list; } State.Choices.Clear(); if (choice.Kind == "vault") { if (choice.Id == "vault_cash") { Cash(list, 0.2, "Vault cash grant", 0uL); } else if (choice.Id == "vault_tickets") { Tickets(list, 3, "Vault ticket shipment"); } else { State.PendingPerkDrafts++; } } else if (choice.Kind == "perk") { if (!State.PerkIds.Contains(choice.Id)) { if (State.PerkIds.Count >= 3) { State.PerkIds.RemoveAt(0); } State.PerkIds.Add(choice.Id); } Issue(list, RewardKind.Notice, 0.0, choice.Name + " added to the crew build.", 0uL); } else if (choice.Kind == "contract") { SetContract(choice.Id); } SyncDescriptions(); MaybeOfferPerks(); UpdateProgress(); RefreshPreview(); return list; } public List RerollModifier() { List list = new List(); if (!State.DayOpen || State.BetItAllUsed || State.QualifyingPlays > 0 || State.PenaltyReserved > 0.0 || State.NegativesResolved || !PerkActive("wildcard") || State.WildcardChapter == State.Chapter) { return list; } if (Active("closing_time")) { Issue(list, RewardKind.Notice, 0.0, "Closing Time is locked after the clock reduction.", 0uL); return list; } State.WildcardChapter = State.Chapter; State.ModifierIds.Clear(); SelectModifiersWithoutClosingTime(); ApplyFactors(list); SelectNegatives(list); SyncDescriptions(); UpdateProgress(); RefreshPreview(); Issue(list, RewardKind.Notice, 0.0, "Wildcard selected " + string.Join(" + ", State.ModifierNames.ToArray()) + ".", 0uL); return list; } public List RerollContract() { List list = new List(); if (!State.DayOpen || State.QualifyingPlays > 0 || !PerkActive("second_opinion") || State.SecondOpinionChapter == State.Chapter || State.ContractId.Length == 0) { return list; } State.SecondOpinionChapter = State.Chapter; string contractId = State.ContractId; string text = DrawContract(); if (text == contractId) { text = DrawContract(); } SetContract(text); UpdateProgress(); Issue(list, RewardKind.Notice, 0.0, "Second Opinion selected " + State.ContractName + ".", 0uL); return list; } public List RerollPerks() { List list = new List(); if (!PerkActive("second_draw") || State.SecondDrawChapter == State.Chapter || State.Choices.Count == 0 || State.Choices[0].Kind != "perk") { return list; } State.SecondDrawChapter = State.Chapter; State.Choices.Clear(); OfferPerks(); Issue(list, RewardKind.Notice, 0.0, "Second Draw refreshed the perk choices.", 0uL); return list; } public int GetEffectsMask() { int num = 0; for (int i = 0; i < Modifiers.Length; i++) { if (Active(Modifiers[i].Id)) { num |= 1 << i; } } if (State.GoldenPayoutPending) { num |= 0x40000000; } return num; } public int GetFeaturedGame() { return State.FeaturedGame; } public bool CanBetItAll() { if (State.DayOpen && !State.DayEnded && State.DayEnabled && State.Players.Length > 0 && State.AvailableGames.Length > 0 && Active("bet_it_all")) { return !State.BetItAllUsed; } return false; } public bool TryConsumeBetItAll() { if (!CanBetItAll()) { return false; } State.BetItAllUsed = true; State.BetItAllConsumedDay = State.Day; return true; } public void PreserveBetItAllUseForDay(int day) { if (day < 0) { throw new ArgumentOutOfRangeException("day"); } State.BetItAllConsumedDay = Math.Max(State.BetItAllConsumedDay, day); State.BetItAllUsed = State.Day >= 0 && State.BetItAllConsumedDay == State.Day; } public List TickNegatives(double elapsedSeconds) { List list = new List(); if (!State.DayOpen || !State.DayEnabled || State.NegativesResolved || !Finite(elapsedSeconds) || elapsedSeconds <= 0.0) { return list; } State.NegativeElapsedSeconds = Math.Min(86400.0, State.NegativeElapsedSeconds + Math.Min(86400.0, elapsedSeconds)); foreach (NegativeProgress negative in State.Negatives) { if (negative.Timed && !negative.Completed && !negative.Failed) { negative.RemainingSeconds = Math.Max(0.0, negative.RemainingSeconds - Math.Min(elapsedSeconds, negative.RemainingSeconds)); if (negative.RemainingSeconds == 0.0) { FailNegative(negative, list); } } } return list; } public List ResolveNegativesAtDayEnd() { List list = new List(); if (!State.DayOpen || !State.DayEnabled || State.NegativesResolved) { return list; } State.NegativesResolved = true; foreach (NegativeProgress negative in State.Negatives) { if (!negative.Completed && !negative.Failed) { FailNegative(negative, list); } } return list; } public Reward FindPendingPenalty(string id) { if (string.IsNullOrEmpty(id)) { return null; } foreach (Reward pendingPenaltyReward in State.PendingPenaltyRewards) { if (pendingPenaltyReward.Id == id) { return pendingPenaltyReward; } } return null; } public bool BindPenalty(string id, double mantissa, long exponent) { Reward reward = FindPendingPenalty(id); if (reward == null || reward.Round != State.Round || reward.Day != State.Day || !Finite(mantissa) || mantissa <= 0.0 || mantissa > 1E+100 || exponent < -1000000 || exponent > 1000000) { return false; } if (reward.CashBound) { if (reward.CashMantissa == mantissa) { return reward.CashExponent == exponent; } return false; } reward.CashMantissa = mantissa; reward.CashExponent = exponent; reward.CashBound = true; return true; } public void ConfirmPenalty(string id, double actualRemovedRatio) { Reward reward = FindPendingPenalty(id); if (reward != null && State.RewardIds.Contains(id) && !State.AppliedRewardIds.Contains(id)) { if (reward.Round != State.Round || reward.Day != State.Day || !reward.CashBound || !Finite(actualRemovedRatio) || actualRemovedRatio < 0.0 || actualRemovedRatio > reward.ReservedAmount + 1E-09) { throw new ArgumentException("Invalid confirmed penalty amount."); } if (actualRemovedRatio == 0.0) { reward.CashMantissa = 0.0; reward.CashExponent = 0L; } else if (!ValidBoundCash(reward)) { throw new ArgumentException("Invalid confirmed native penalty amount."); } reward.Amount = Math.Min(reward.ReservedAmount, actualRemovedRatio); State.PendingPenaltyRewards.Remove(reward); State.PenaltyReceipts.Add(reward); Trim(State.PenaltyReceipts, 64); State.PenaltyDelivered = Math.Min(State.PenaltyReserved, State.PenaltyDelivered + reward.Amount); Discover(State.AppliedRewardIds, id); Trim(State.AppliedRewardIds, 2048); } } private void CancelPendingPenalties() { foreach (Reward pendingPenaltyReward in State.PendingPenaltyRewards) { Discover(State.AppliedRewardIds, pendingPenaltyReward.Id); } State.PendingPenaltyRewards.Clear(); Trim(State.AppliedRewardIds, 2048); } private void SelectNegatives(List output) { State.Negatives.Clear(); if (!State.DayEnabled || !State.ActiveRules.NegativesEnabled || !State.NegativeDayChosen || State.Players.Length == 0 || State.Players.Length > 32 || State.AvailableGames.Length == 0) { return; } if (State.NegativeRandomState == 0) { State.NegativeRandomState = State.RandomState ^ 0xA3C59AC3u; } double[] array = new double[State.ModifierIds.Count]; double num = 0.0; int num2 = 0; for (int i = 0; i < array.Length; i++) { if (ContinuousBudget(State.ModifierIds[i])) { num2++; continue; } array[i] = FixedRisk(State.ModifierIds[i]); num += array[i]; } double maxBonusQuotaFraction = State.ActiveRules.MaxBonusQuotaFraction; if (num > maxBonusQuotaFraction && num > 0.0) { for (int j = 0; j < array.Length; j++) { array[j] *= maxBonusQuotaFraction / num; } } double num3 = Math.Max(0.0, maxBonusQuotaFraction - num); for (int k = 0; k < array.Length; k++) { if (ContinuousBudget(State.ModifierIds[k])) { array[k] = ((num2 == 0) ? 0.0 : (num3 / (double)num2)); } } for (int l = 0; l < array.Length; l++) { string text = DrawNegative(); Definition definition = Find(NegativeModifiers, text); if (definition == null) { break; } string text2 = State.ModifierIds[l]; bool flag = text.StartsWith("timed_", StringComparison.Ordinal); string text3 = (flag ? text.Substring(6) : text); NegativeProgress negativeProgress = new NegativeProgress(); negativeProgress.Id = text; negativeProgress.Name = definition.Name; negativeProgress.Description = definition.Description; negativeProgress.PairedModifierId = text2; negativeProgress.RiskBasis = (ContinuousBudget(text2) ? "Share of the paired modifier's potential daily cash budget" : FixedRiskBasis(text2)); negativeProgress.BaseRiskRatio = array[l]; negativeProgress.RiskRatio = array[l] * SeverityScale(); negativeProgress.Timed = flag; negativeProgress.Required = text3 switch { "six_plays" => 6, "variety" => 2, "participation" => (State.Players.Length == 1) ? 3 : State.Players.Length, _ => 1, }; negativeProgress.ProfitTarget = ((text3 == "net_profit") ? 0.03 : 0.0); negativeProgress.Roster = (ulong[])State.Players.Clone(); negativeProgress.PenaltyId = ""; NegativeProgress negativeProgress2 = negativeProgress; negativeProgress2.DeadlineSeconds = (flag ? (State.NegativeDaySeconds * State.ClosingTimeFactor * 0.6) : 0.0); negativeProgress2.RemainingSeconds = (flag ? Math.Max(0.0, negativeProgress2.DeadlineSeconds - State.NegativeElapsedSeconds) : 0.0); State.Negatives.Add(negativeProgress2); bool flag2 = false; foreach (Cooldown negativeCooldown in State.NegativeCooldowns) { if (negativeCooldown.Id == text) { negativeCooldown.Round = State.Round; flag2 = true; break; } } if (!flag2) { State.NegativeCooldowns.Add(new Cooldown { Id = text, Round = State.Round }); } if (flag && negativeProgress2.RemainingSeconds == 0.0) { FailNegative(negativeProgress2, output); } } } private void ChooseNegativeDay() { State.NegativeDayChosen = false; if (!State.DayEnabled || !State.ActiveRules.NegativesEnabled || State.Players.Length == 0 || State.Players.Length > 32 || State.AvailableGames.Length == 0 || State.ModifierIds.Count == 0) { return; } double negativeDayPercent = State.ActiveRules.NegativeDayPercent; if (negativeDayPercent <= 0.0) { return; } if (negativeDayPercent >= 100.0) { State.NegativeDayChosen = true; return; } uint num = State.NegativeDayRandomState; if (num == 0) { num = State.RandomState ^ 0x735A2D97; } if (num == 0) { num = 1831565813u; } num ^= num << 13; num ^= num >> 17; num ^= num << 5; State.NegativeDayRandomState = ((num == 0) ? 1831565813u : num); State.NegativeDayChosen = (double)(State.NegativeDayRandomState % 10000) < negativeDayPercent * 100.0; } public List AwardNpcKill(string npcKey, ulong shooter, double displayedQuotaFactor) { List list = new List(); if (!State.DayOpen || !State.DayEnabled || State.DayEnded || shooter == 0 || !ValidNpcKey(npcKey) || State.KilledNpcKeys.Contains(npcKey) || State.KilledNpcKeys.Count >= 512 || State.PendingCashRewards.Count >= 256 || !Finite(displayedQuotaFactor) || displayedQuotaFactor < 0.5 || displayedQuotaFactor > 1.5) { return list; } double num = displayedQuotaFactor * 0.01; State.KilledNpcKeys.Add(npcKey); State.NpcCashReserved += num; resultActor = shooter; Issue(list, RewardKind.Cash, num, "NPC eliminated", shooter); list[list.Count - 1].NpcKill = true; list[list.Count - 1].TaskCompleted = true; resultActor = 0uL; return list; } public bool RecordNpcDeath(string npcKey) { if (!State.DayOpen || !State.DayEnabled || State.DayEnded || !ValidNpcKey(npcKey) || State.KilledNpcKeys.Contains(npcKey) || State.KilledNpcKeys.Count >= 512) { return false; } State.KilledNpcKeys.Add(npcKey); return true; } private static bool ValidNpcKey(string key) { if (string.IsNullOrEmpty(key) || key.Length > 16 || !key.StartsWith("n1/f", StringComparison.Ordinal)) { return false; } int num = key.IndexOf("/s", 4, StringComparison.Ordinal); if (num < 5 || !int.TryParse(key.Substring(4, num - 4), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(key.Substring(num + 2), NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || result < 1 || result > 64 || result2 < 0 || result2 >= 512) { return false; } return key == "n1/f" + result.ToString(CultureInfo.InvariantCulture) + "/s" + result2.ToString(CultureInfo.InvariantCulture); } private double SeverityScale() { if (State.ActiveRules.PenaltySeverity != 1) { if (State.ActiveRules.PenaltySeverity != 3) { return 1.0; } return 1.5; } return 0.5; } private static bool ContinuousBudget(string id) { switch (id) { default: return id == "rebate_night"; case "house_match": case "hot_table": case "lucky_rotation": return true; } } private static double FixedRisk(string id) { switch (id) { case "variety_show": case "comeback_night": return 0.06; case "breather": return 0.15; case "closing_time": return 0.2; default: return 0.05; } } private static string FixedRiskBasis(string id) { switch (id) { case "bet_it_all": return "5% of the original target as a reference for the one-use all-in privilege; this is not a cash reward"; case "crew_relay": case "streak_night": case "frugal_friends": case "variety_show": case "comeback_night": return "Matches the paired modifier's fixed cash grant"; case "breather": case "closing_time": return "Matches the paired modifier's original-target reduction"; default: return "5% of the original target as a reference for a ticket or perk benefit; this is not an exchange rate"; } } private bool EligibleNegative(string id) { if (Find(NegativeModifiers, id) == null) { return false; } bool flag = id.StartsWith("timed_", StringComparison.Ordinal); if (flag && !State.ActiveRules.TimedNegativesEnabled) { return false; } if (!((flag ? id.Substring(6) : id) != "variety")) { return State.AvailableGames.Length >= 2; } return true; } private string DrawNegative() { if (State.NegativeDeck.Count == 0) { RefillNegatives(); } for (int i = 0; i < 2; i++) { for (int j = 0; j < State.NegativeDeck.Count; j++) { string text = State.NegativeDeck[j]; if (EligibleNegative(text) && !SelectedNegative(text) && State.Round - LastNegative(text) > 1) { State.NegativeDeck.RemoveAt(j); return text; } } RefillNegatives(); } string text2 = ""; int num = int.MaxValue; foreach (string item in State.NegativeDeck) { if (EligibleNegative(item) && !SelectedNegative(item) && LastNegative(item) < num) { text2 = item; num = LastNegative(item); } } State.NegativeDeck.Remove(text2); return text2; } private bool SelectedNegative(string id) { foreach (NegativeProgress negative in State.Negatives) { if (negative.Id == id) { return true; } } return false; } private int LastNegative(string id) { foreach (Cooldown negativeCooldown in State.NegativeCooldowns) { if (negativeCooldown.Id == id) { return negativeCooldown.Round; } } return -100; } private void RefillNegatives() { State.NegativeDeck.Clear(); Definition[] negativeModifiers = NegativeModifiers; foreach (Definition definition in negativeModifiers) { if (EligibleNegative(definition.Id)) { State.NegativeDeck.Add(definition.Id); } } for (int num = State.NegativeDeck.Count - 1; num > 0; num--) { int index = NextNegative(num + 1); string value = State.NegativeDeck[num]; State.NegativeDeck[num] = State.NegativeDeck[index]; State.NegativeDeck[index] = value; } } private int NextNegative(int count) { uint negativeRandomState = State.NegativeRandomState; negativeRandomState ^= negativeRandomState << 13; negativeRandomState ^= negativeRandomState >> 17; negativeRandomState ^= negativeRandomState << 5; State.NegativeRandomState = ((negativeRandomState == 0) ? 1831565813u : negativeRandomState); return (int)(State.NegativeRandomState % (uint)count); } private void AdvanceNegatives(ResultInput input, double net, List output) { if (State.NegativesResolved) { return; } foreach (NegativeProgress negative in State.Negatives) { if (negative.Completed || negative.Failed) { continue; } switch (negative.Timed ? negative.Id.Substring(6) : negative.Id) { case "participation": if (Contains(negative.Roster, input.Player)) { if (!negative.Participants.Contains(input.Player)) { negative.Participants.Add(input.Player); } negative.Count = ((negative.Roster.Length == 1) ? Math.Min(negative.Required, negative.Count + 1) : negative.Participants.Count); } break; case "variety": if (!negative.Games.Contains(input.Game)) { negative.Games.Add(input.Game); } negative.Count = Math.Min(negative.Required, negative.Games.Count); break; case "six_plays": negative.Count = Math.Min(negative.Required, negative.Count + 1); break; case "net_profit": negative.Profit = Math.Max(-1E+100, Math.Min(1E+100, negative.Profit + net)); negative.Count = ((negative.Profit + 1E-12 >= negative.ProfitTarget) ? 1 : 0); break; } if (negative.Count >= negative.Required) { negative.Completed = true; negative.Completer = input.Player; Issue(output, RewardKind.Notice, 0.0, negative.Name + " complete; its shared cash loss was avoided.", 0uL); output[output.Count - 1].TaskCompleted = true; } } } private void FailNegative(NegativeProgress task, List output) { if (!task.Completed && !task.Failed) { task.Failed = true; double num = Math.Min(task.RiskRatio, Math.Max(0.0, State.ActiveRules.MaxBonusQuotaFraction * SeverityScale() - State.PenaltyReserved)); if (num <= 1E-12) { Issue(output, RewardKind.Notice, 0.0, task.Name + " missed; no cash remained at risk.", 0uL); return; } State.PenaltyReserved += num; Issue(output, RewardKind.PenaltyCash, num, task.Name + " missed", 0uL); Reward reward = output[output.Count - 1]; reward.Actor = 0uL; reward.ReservedAmount = num; task.PenaltyId = reward.Id; } } public void MarkRewardApplied(string id) { if (string.IsNullOrEmpty(id) || !State.RewardIds.Contains(id) || State.AppliedRewardIds.Contains(id) || FindPendingPenalty(id) != null) { return; } foreach (Reward penaltyReceipt in State.PenaltyReceipts) { if (penaltyReceipt.Id == id) { return; } } foreach (Reward cashReceipt in State.CashReceipts) { if (cashReceipt.Id == id) { return; } } Reward reward = null; foreach (Reward pendingCashReward in State.PendingCashRewards) { if (pendingCashReward.Id == id) { reward = pendingCashReward; break; } } if (reward != null) { if (!reward.CashBound || !ValidBoundCash(reward)) { return; } State.PendingCashRewards.Remove(reward); State.CashReceipts.Add(reward); Trim(State.CashReceipts, 64); if (reward.Round == State.Round && reward.Day == State.Day) { if (reward.NpcKill) { State.NpcCashDelivered = Math.Min(State.NpcCashReserved, State.NpcCashDelivered + reward.Amount); } else { State.CashDelivered = Math.Min(State.CashAwarded, State.CashDelivered + reward.Amount); } } } Discover(State.AppliedRewardIds, id); Trim(State.AppliedRewardIds, 2048); } public string BuildSummary() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Chaos Night | Round ").Append(State.Round).Append(" | Chapter ") .Append(State.Chapter); if (!State.DayEnabled) { stringBuilder.Append("\nChaos is disabled for this round."); return stringBuilder.ToString(); } for (int i = 0; i < State.ModifierNames.Count; i++) { stringBuilder.Append("\n").Append(State.ModifierNames[i]).Append(": ") .Append(State.ModifierDescriptions[i]); } if (State.ModifierIds.Contains("hot_table") || State.ModifierIds.Contains("lucky_rotation")) { stringBuilder.Append("\nFeatured game: ").Append(State.FeaturedGame); } stringBuilder.Append("\nTarget: ").Append(Percent(State.QuotaFactor)).Append(" of original | Bonus budget reserved: ") .Append(Percent(State.CashAwarded)) .Append(" / ") .Append(Percent((State.ActiveRules == null) ? 0.35 : State.ActiveRules.MaxBonusQuotaFraction)); stringBuilder.Append("\nCash delivery: ").Append(Percent(State.CashDelivered)).Append(State.LegacyCash ? " legacy shared cash; attribution unavailable" : " confirmed; ") .Append(State.PendingCashRewards.Count) .Append(" payments waiting"); if (!string.IsNullOrEmpty(State.ContractName)) { stringBuilder.Append("\n").Append(State.ContractName).Append(": ") .Append(State.ContractDescription) .Append("\n") .Append(State.ContractProgress); } if (State.PerkNames.Count > 0) { stringBuilder.Append("\nCrew perks: ").Append(string.Join(", ", State.PerkNames.ToArray())); } if (State.NextModifierPreview.Length > 0) { stringBuilder.Append("\nScout preview: ").Append(State.NextModifierPreview); } if (State.GoldenPayoutPending) { stringBuilder.Append("\nGolden Payout: waiting for the next qualifying profitable wager."); } for (int j = 0; j < State.Choices.Count; j++) { stringBuilder.Append("\nChoice ").Append(j + 1).Append(": ") .Append(State.Choices[j].Name) .Append(" — ") .Append(State.Choices[j].Description); } stringBuilder.Append("\nRun: ").Append(State.DaysSurvived).Append(" rounds survived, ") .Append(State.ContractsCompleted) .Append(" contracts, ") .Append(State.JackpotsFound) .Append(" jackpots."); return stringBuilder.ToString(); } private void SelectModifiers() { SelectModifiers(excludeClosing: false); } private void SelectModifiersWithoutClosingTime() { SelectModifiers(excludeClosing: true); } private void SelectModifiers(bool excludeClosing) { string text = DrawModifier(excludeClosing, ""); if (text.Length == 0) { return; } AddModifier(text); if (text == "double_trouble" || (State.ActiveRules.Intensity >= 7 && State.Chapter >= 3 && Next(100) < 35)) { string text2 = DrawModifier(excludeClosing, text); if (text2.Length > 0) { AddModifier(text2); } } } private string DrawModifier(bool excludeClosing, string companion) { for (int i = 0; i < 2; i++) { if (State.ModifierDeck.Count == 0) { RefillModifiers(); } for (int j = 0; j < State.ModifierDeck.Count; j++) { string text = State.ModifierDeck[j]; if (EligibleModifier(text) && (!excludeClosing || !(text == "closing_time")) && Compatible(text, companion)) { int num = LastModifier(text); if (State.Round - num > 2 || !HasAlternative(companion, excludeClosing)) { State.ModifierDeck.RemoveAt(j); return text; } } } if (HasAlternative(companion, excludeClosing)) { RefillModifiers(); continue; } for (int k = 0; k < State.ModifierDeck.Count; k++) { string text2 = State.ModifierDeck[k]; if (EligibleModifier(text2) && (!excludeClosing || !(text2 == "closing_time")) && Compatible(text2, companion)) { State.ModifierDeck.RemoveAt(k); return text2; } } RefillModifiers(); } return ""; } private bool HasAlternative(string companion, bool excludeClosing) { Definition[] modifiers = Modifiers; foreach (Definition definition in modifiers) { if (EligibleModifier(definition.Id) && (!excludeClosing || !(definition.Id == "closing_time")) && Compatible(definition.Id, companion) && State.Round - LastModifier(definition.Id) > 2) { return true; } } return false; } private bool EligibleModifier(string id) { if (Find(Modifiers, id) == null) { return false; } if (id == "closing_time" && !State.ActiveRules.ClosingTimeEnabled) { return false; } if (id == "frugal_friends" && !State.ActiveRules.PurchasesSupported) { return false; } if (State.AvailableGames.Length == 0 && id != "closing_time" && id != "pressure_cooker" && id != "breather" && id != "ticket_rush" && id != "plain_luck") { return false; } string[] array = State.ActiveRules.DisabledModifiers.Split(','); string[] array2 = array; foreach (string text in array2) { if (string.Equals(text.Trim(), id, StringComparison.OrdinalIgnoreCase)) { return false; } } return true; } public static bool Compatible(string a, string b) { if (string.IsNullOrEmpty(b)) { return true; } if (a == b) { return false; } if (a == "double_trouble") { return MildModifier(b); } if (b == "double_trouble") { return MildModifier(a); } if (QuotaModifier(a) && QuotaModifier(b)) { return false; } if ((a == "hot_table" && b == "lucky_rotation") || (b == "hot_table" && a == "lucky_rotation")) { return false; } if ((a == "plain_luck" && b == "double_trouble") || (b == "plain_luck" && a == "double_trouble")) { return false; } return true; } private static bool MildModifier(string id) { switch (id) { default: return id == "frugal_friends"; case "hot_table": case "variety_show": case "crew_relay": case "risk_dividend": case "streak_night": case "ticket_rush": return true; } } private static bool QuotaModifier(string id) { switch (id) { default: return id == "breather"; case "house_match": case "closing_time": case "pressure_cooker": return true; } } private void AddModifier(string id) { State.ModifierIds.Add(id); Discover(State.DiscoveredModifiers, id); bool flag = false; foreach (Cooldown modifierCooldown in State.ModifierCooldowns) { if (modifierCooldown.Id == id) { modifierCooldown.Round = State.Round; flag = true; break; } } if (!flag) { State.ModifierCooldowns.Add(new Cooldown { Id = id, Round = State.Round }); } } private int LastModifier(string id) { foreach (Cooldown modifierCooldown in State.ModifierCooldowns) { if (modifierCooldown.Id == id) { return modifierCooldown.Round; } } return -100; } private void RefillModifiers() { State.ModifierDeck.Clear(); Definition[] modifiers = Modifiers; foreach (Definition definition in modifiers) { if (EligibleModifier(definition.Id)) { State.ModifierDeck.Add(definition.Id); } } Shuffle(State.ModifierDeck); } private void ApplyFactors(List output) { double num = 1.0; if (Active("house_match")) { num = 1.1; } if (Active("pressure_cooker")) { num = 1.1 + 0.015 * (double)Math.Min(10, State.ActiveRules.Intensity + Math.Min(4, State.Chapter - 1)); } if (Active("breather")) { num = 0.85; } if (Active("closing_time")) { num = 0.8; } State.QuotaFactor = num; Issue(output, RewardKind.QuotaFactor, num, "Chaos target set to " + Percent(num) + " of the original target.", 0uL); if (Active("closing_time") && State.ClosingTimeFactor == 1.0) { State.ClosingTimeFactor = 0.8; Issue(output, RewardKind.ClosingTimeFactor, 0.8, "Closing Time: 20% shorter round, paired with 20% lower original target.", 0uL); } } private string DrawContract() { if (State.ContractDeck.Count == 0) { Definition[] contracts = Contracts; foreach (Definition definition in contracts) { if (definition.Id != "budget_night" || State.ActiveRules.PurchasesSupported) { State.ContractDeck.Add(definition.Id); } } Shuffle(State.ContractDeck); } for (int j = 0; j < State.ContractDeck.Count; j++) { string text = State.ContractDeck[j]; if (!(text == "budget_night") || State.ActiveRules.PurchasesSupported) { State.ContractDeck.RemoveAt(j); return text; } } return ""; } private void SetContract(string id) { Definition definition = Find(Contracts, id); State.ContractId = ((definition == null) ? "" : definition.Id); State.ContractName = ((definition == null) ? "" : definition.Name); State.ContractDescription = ((definition == null) ? "" : definition.Description); State.ContractCompleted = false; State.ContractFeaturedGame = State.FeaturedGame; UpdateProgress(); } private bool ContractSatisfied(bool quotaMet) { if (State.ContractCompleted || State.ContractId.Length == 0) { return false; } switch (State.ContractId) { case "casino_tour": return GameCount(wins: false) >= Math.Min(3, State.AvailableGames.Length); case "everybody_in": return CrewReady(wins: false); case "profit_ladder": return State.NativeProfit >= State.ContractTarget; case "recovery_crew": return State.ComebackCompleted; case "budget_night": if (quotaMet) { return State.Purchases <= 0.35; } return false; case "small_stakes": return State.SmallStakeProfit >= State.ContractTarget * 0.5; case "high_roller": return State.HighRollerPlays >= 3; case "team_streak": return State.BestDayStreak >= 3; case "clean_run": if (quotaMet) { return !State.RescueUsed; } return false; case "featured_game": return FeaturedProfit() >= State.ContractTarget * 0.5; case "strong_finish": return State.NativeProfit >= 1.0 + State.ContractTarget * 0.5; case "balanced_crew": return CrewReady(wins: true); default: return false; } } private void CompleteContract(List output) { if (!State.ContractCompleted) { State.ContractCompleted = true; State.ContractsCompleted++; Discover(State.CompletedContractIds, State.ContractId); double num = 0.06 + 0.01 * (double)Math.Min(4, State.Chapter - 1); if (PerkActive("team_dividend")) { num *= 1.25; } Cash(output, num, State.ContractName + " complete", 0uL); if (State.ContractId == "high_roller") { Tickets(output, 1, "High Roller contract ticket"); } if (PerkActive("house_credit") && State.HouseCreditChapter != State.Chapter) { State.HouseCreditChapter = State.Chapter; Cash(output, 0.04, "House Credit", 0uL); } Issue(output, RewardKind.Notice, 0.0, State.ContractName + " completed.", 0uL); } } private void TrackBalance(double balance, double nativeNet) { if (!State.BalanceKnown) { State.PeakBalance = Math.Max(0.0, balance - nativeNet); State.LowBalance = balance; State.BalanceKnown = true; } State.PeakBalance = Math.Max(State.PeakBalance, balance); State.LowBalance = Math.Min(State.LowBalance, balance); if (State.PeakBalance - balance >= 0.1) { State.HadSetback = true; } if (State.HadSetback && balance >= State.LowBalance + 0.1) { State.ComebackCompleted = true; State.BiggestRecoveryRatio = Math.Max(State.BiggestRecoveryRatio, balance - State.LowBalance); } State.LastBalance = balance; } private void CheckJackpot(List output) { if (State.JackpotChecked) { return; } State.JackpotChecked = true; if (State.Round - State.LastJackpotRound < 4 || State.ActiveRules.JackpotPercent <= 0.0 || (double)Next(10000) >= State.ActiveRules.JackpotPercent * 100.0) { return; } List list = new List(); list.Add("vault"); list.Add("golden_payout"); list.Add("lucky_shipment"); list.Add("debt_relief"); List list2 = list; if (State.NativeLosses > 0.0) { list2.Add("house_refund"); } string text = list2[Next(list2.Count)]; State.LastJackpotRound = State.Round; State.JackpotsFound++; Discover(State.DiscoveredJackpots, text); Definition definition = Find(Jackpots, text); Issue(output, RewardKind.Notice, 0.0, "JACKPOT: " + definition.Name + ". " + definition.Description, 0uL); switch (text) { case "vault": if (State.Choices.Count == 0) { State.ChoiceRound = State.Round; State.Choices.Add(new Choice { Id = "vault_cash", Name = "Shared cash", Kind = "vault", Description = "Up to 20% of the original target, within the remaining shared cash cap." }); State.Choices.Add(new Choice { Id = "vault_tickets", Name = "Ticket shipment", Kind = "vault", Description = "Up to three tickets, within the remaining daily ticket cap." }); State.Choices.Add(new Choice { Id = "vault_perk", Name = "Crew perk", Kind = "vault", Description = "Open an additional perk draft." }); } else { State.PendingPerkDrafts++; Issue(output, RewardKind.Notice, 0.0, "The vault queued a perk shipment behind the open choice.", 0uL); } break; case "golden_payout": State.GoldenPayoutPending = true; break; case "house_refund": State.RescueUsed = true; Cash(output, State.NativeLosses * 0.5, "House Refund", 0uL); break; case "lucky_shipment": State.PendingPerkDrafts++; MaybeOfferPerks(); break; case "debt_relief": State.RescueUsed = true; State.QuotaFactor = Math.Max(0.65, State.QuotaFactor - 0.1); Issue(output, RewardKind.QuotaFactor, State.QuotaFactor, "Debt Relief lowered the original-target factor to " + Percent(State.QuotaFactor) + ".", 0uL); break; } } private void OfferContractChoices() { State.ChoiceRound = State.Round; for (int i = 0; i < 2; i++) { string id = DrawContract(); Definition definition = Find(Contracts, id); if (definition != null) { State.Choices.Add(new Choice { Id = id, Name = definition.Name, Description = definition.Description, Kind = "contract" }); } } } private void MaybeOfferPerks() { if (State.Choices.Count == 0 && State.PendingPerkDrafts > 0) { State.PendingPerkDrafts--; OfferPerks(); } } private void OfferPerks() { List list = new List(); Definition[] perks = Perks; foreach (Definition definition in perks) { if (!State.PerkIds.Contains(definition.Id)) { list.Add(definition.Id); } } Shuffle(list); State.ChoiceRound = State.Round; for (int j = 0; j < Math.Min(3, list.Count); j++) { Definition definition2 = Find(Perks, list[j]); string text = definition2.Description; if (State.PerkIds.Count >= 3) { text = text + " Replaces the oldest perk: " + Find(Perks, State.PerkIds[0]).Name + "."; } State.Choices.Add(new Choice { Id = definition2.Id, Name = definition2.Name, Description = text, Kind = "perk" }); } } private bool PerkActive(string id) { if (State.DayEnabled && State.PerkIds.Contains(id)) { return !Active("plain_luck"); } return false; } private bool Active(string id) { return State.ModifierIds.Contains(id); } private bool Effect(string id, int mask) { for (int i = 0; i < Modifiers.Length; i++) { if (Modifiers[i].Id == id) { if (Active(id)) { return (mask & (1 << i)) != 0; } return false; } } return false; } private bool CrewReady(bool wins) { if (State.Players.Length == 0) { return false; } if (State.Players.Length == 1) { PlayerProgress playerProgress = FindPlayer(State.Players[0]); if (!wins) { return playerProgress.Plays >= 3; } return playerProgress.Wins >= 2; } int num = 0; ulong[] players = State.Players; foreach (ulong id in players) { PlayerProgress playerProgress2 = FindPlayer(id); if (wins ? (playerProgress2.Wins > 0) : (playerProgress2.Plays > 0)) { num++; } } if (!wins) { return num == State.Players.Length; } return num >= Math.Min(2, State.Players.Length); } private int GameCount(bool wins) { int num = 0; foreach (GameProgress item in State.GameProgress) { if (wins ? (item.Wins > 0) : (item.Plays > 0)) { num++; } } return num; } private double FeaturedProfit() { foreach (GameProgress item in State.GameProgress) { if (item.Id == State.ContractFeaturedGame) { return item.Profit; } } return 0.0; } private PlayerProgress FindPlayer(ulong id) { foreach (PlayerProgress item in State.PlayerProgress) { if (item.Id == id) { return item; } } throw new InvalidOperationException("Unknown player."); } private GameProgress FindGame(int id) { foreach (GameProgress item in State.GameProgress) { if (item.Id == id) { return item; } } throw new InvalidOperationException("Unknown game."); } private void Cash(List output, double amount, string description, ulong player = 0uL) { if (!Finite(amount) || amount <= 0.0) { return; } amount = Math.Min(amount, Math.Max(0.0, State.ActiveRules.MaxBonusQuotaFraction - State.CashAwarded)); if (amount <= 1E-12) { return; } ulong[] array = ((player == 0) ? State.Players : new ulong[1] { player }); if (array == null || array.Length == 0 || (player != 0 && !Contains(State.Players, player))) { return; } if (array.Length > 256 - State.PendingCashRewards.Count) { Issue(output, RewardKind.Notice, 0.0, "Cash payment queue is full; no new cash was allocated.", 0uL); return; } State.CashAwarded += amount; double num = amount / (double)array.Length; double num2 = 0.0; for (int i = 0; i < array.Length; i++) { double num3 = ((i == array.Length - 1) ? Math.Max(0.0, amount - num2) : num); num2 += num3; Issue(output, RewardKind.Cash, num3, description, array[i]); } } private void CashOnce(List output, string key, double amount, string description) { string item = State.RunId + "/d" + State.Round + "/once/" + key; if (!State.RewardIds.Contains(item)) { State.RewardIds.Add(item); Cash(output, amount, description, 0uL); } } private void Tickets(List output, int amount, string description) { amount = Math.Min(amount, State.ActiveRules.MaxTicketsPerDay - State.TicketsAwarded); if (amount > 0) { State.TicketsAwarded += amount; Issue(output, RewardKind.Tickets, amount, description, 0uL); } } private void Issue(List output, RewardKind kind, double amount, string description, ulong player = 0uL) { string text = State.RunId + "/d" + State.Round + "/a" + ++State.AwardSequence; Reward reward = new Reward(); reward.Id = text; reward.Kind = kind; reward.Amount = amount; reward.Description = description; reward.Player = player; reward.Day = State.Day; reward.Round = State.Round; reward.Actor = resultActor; reward.TaskCompleted = description != null && description.IndexOf(" complete", StringComparison.Ordinal) >= 0; Reward item = reward; State.RewardIds.Add(text); if (kind == RewardKind.Cash) { State.PendingCashRewards.Add(item); } if (kind == RewardKind.PenaltyCash) { State.PendingPenaltyRewards.Add(item); } TrimRewardIds(); output.Add(item); if (kind == RewardKind.Notice) { State.RecentEvents.Add(description); Trim(State.RecentEvents, 32); } } private void TrimRewardIds() { while (State.RewardIds.Count > 2048) { int num = -1; for (int i = 0; i < State.RewardIds.Count; i++) { string text = State.RewardIds[i]; bool flag = false; foreach (Reward pendingCashReward in State.PendingCashRewards) { if (pendingCashReward.Id == text) { flag = true; break; } } if (!flag) { foreach (Reward cashReceipt in State.CashReceipts) { if (cashReceipt.Id == text) { flag = true; break; } } } if (!flag) { foreach (Reward pendingPenaltyReward in State.PendingPenaltyRewards) { if (pendingPenaltyReward.Id == text) { flag = true; break; } } } if (!flag) { foreach (Reward penaltyReceipt in State.PenaltyReceipts) { if (penaltyReceipt.Id == text) { flag = true; break; } } } if (!flag) { num = i; break; } } if (num < 0) { throw new InvalidOperationException("Cash receipt history exceeded its bounded capacity."); } State.RewardIds.RemoveAt(num); } } private static bool ValidBoundCash(Reward reward) { if (Finite(reward.CashMantissa) && reward.CashMantissa > 0.0 && reward.CashMantissa <= 1E+100 && reward.CashExponent >= -1000000) { return reward.CashExponent <= 1000000; } return false; } private void ValidateCashLedger() { if (!Finite(State.CashDelivered) || State.CashDelivered < 0.0 || State.CashDelivered > State.CashAwarded + 1E-09 || !Finite(State.NpcCashReserved) || !Finite(State.NpcCashDelivered) || State.NpcCashReserved < 0.0 || State.NpcCashDelivered < 0.0 || State.NpcCashReserved > 38.400000000999995 || State.NpcCashDelivered > State.NpcCashReserved + 1E-09 || State.KilledNpcKeys.Count > 512 || State.PendingCashRewards.Count > 256 || State.CashReceipts.Count > 64) { throw new ArgumentException("Invalid cash-delivery checkpoint counters."); } HashSet hashSet = new HashSet(); foreach (string killedNpcKey in State.KilledNpcKeys) { if (!ValidNpcKey(killedNpcKey) || !hashSet.Add(killedNpcKey)) { throw new ArgumentException("Invalid NPC kill checkpoint keys."); } } HashSet seen = new HashSet(); double num = 0.0; double num2 = 0.0; foreach (Reward pendingCashReward in State.PendingCashRewards) { ValidateCashReward(pendingCashReward, seen); if (State.AppliedRewardIds.Contains(pendingCashReward.Id)) { throw new ArgumentException("Cash is both pending and applied."); } if (pendingCashReward.Round == State.Round && pendingCashReward.Day == State.Day) { if (pendingCashReward.NpcKill) { num2 += pendingCashReward.Amount; } else { num += pendingCashReward.Amount; } } } foreach (Reward cashReceipt in State.CashReceipts) { ValidateCashReward(cashReceipt, seen); if (!cashReceipt.CashBound || !ValidBoundCash(cashReceipt)) { throw new ArgumentException("Unbound confirmed cash receipt."); } } if (num + State.CashDelivered > State.CashAwarded + 1E-09) { throw new ArgumentException("Cash pending and delivered exceeds its reserved grant."); } if (num2 + State.NpcCashDelivered > State.NpcCashReserved + 1E-09) { throw new ArgumentException("NPC cash pending and delivered exceeds its reserved grant."); } } private void ValidateCashReward(Reward reward, HashSet seen) { if (reward == null || string.IsNullOrEmpty(reward.Id) || !seen.Add(reward.Id) || !State.RewardIds.Contains(reward.Id) || reward.Kind != RewardKind.Cash || reward.Player == 0 || !Finite(reward.Amount) || reward.Amount <= 0.0 || reward.Amount > 1.0 || reward.Round < 1 || reward.Round > State.Round || reward.Day < 0 || reward.Day > State.Day || (reward.CashBound && !ValidBoundCash(reward)) || (!reward.CashBound && (reward.CashMantissa != 0.0 || reward.CashExponent != 0))) { throw new ArgumentException("Invalid cash reward payload or recipient."); } } private void ValidateNegatives() { if (State.NegativeVersion < 0 || State.NegativeVersion > 1) { throw new ArgumentException("Unsupported negative-task checkpoint."); } if (State.NegativeScheduleVersion < 0 || State.NegativeScheduleVersion > 1) { throw new ArgumentException("Unsupported negative-day schedule."); } if (State.NegativeVersion == 0) { if (State.Negatives.Count != 0 || State.PendingPenaltyRewards.Count != 0 || State.PenaltyReceipts.Count != 0 || State.PenaltyReserved != 0.0 || State.PenaltyDelivered != 0.0) { throw new ArgumentException("Unversioned negative-task ledger."); } return; } double num = State.ActiveRules.MaxBonusQuotaFraction * SeverityScale(); if (!Finite(State.NegativeDaySeconds) || State.NegativeDaySeconds < 0.0 || State.NegativeDaySeconds > 86400.0 || !Finite(State.NegativeElapsedSeconds) || State.NegativeElapsedSeconds < 0.0 || State.NegativeElapsedSeconds > 86400.0 || !Finite(State.PenaltyReserved) || !Finite(State.PenaltyDelivered) || State.PenaltyDelivered < 0.0 || State.PenaltyReserved < 0.0 || State.PenaltyReserved > num + 1E-09 || State.PenaltyDelivered > State.PenaltyReserved + 1E-09 || State.Negatives.Count > 2 || State.PendingPenaltyRewards.Count > 2 || State.PenaltyReceipts.Count > 64 || State.NegativeCooldowns.Count > NegativeModifiers.Length || State.NegativeDeck.Count > NegativeModifiers.Length || !KnownIds(State.NegativeDeck, NegativeModifiers)) { throw new ArgumentException("Invalid negative-task checkpoint counters."); } int num2 = ((State.DayEnabled && State.ActiveRules.NegativesEnabled && State.NegativeDayChosen && State.Players.Length > 0 && State.Players.Length <= 32 && State.AvailableGames.Length > 0) ? State.ModifierIds.Count : 0); if (State.Negatives.Count != num2) { throw new ArgumentException("Negative tasks do not match their positive daily modifiers."); } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); double num3 = 0.0; foreach (NegativeProgress negative in State.Negatives) { if (negative == null || Find(NegativeModifiers, negative.Id) == null || !hashSet.Add(negative.Id) || !hashSet2.Add(negative.PairedModifierId) || !State.ModifierIds.Contains(negative.PairedModifierId) || negative.Required < 1 || negative.Required > 32 || negative.Count < 0 || negative.Count > negative.Required || (negative.Completed && negative.Failed) || negative.Participants == null || negative.Games == null || negative.Roster == null || negative.Roster.Length < 1 || negative.Roster.Length > 32 || negative.Participants.Count > 32 || negative.Games.Count > 32 || negative.Name == null || negative.Description == null || negative.RiskBasis == null || !Finite(negative.Profit) || Math.Abs(negative.Profit) > 1E+100 || !Finite(negative.ProfitTarget) || negative.ProfitTarget < 0.0 || negative.ProfitTarget > 1.0 || !Finite(negative.RiskRatio) || !Finite(negative.BaseRiskRatio) || negative.BaseRiskRatio < 0.0 || negative.RiskRatio < 0.0 || Math.Abs(negative.RiskRatio - negative.BaseRiskRatio * SeverityScale()) > 1E-09 || !Finite(negative.DeadlineSeconds) || !Finite(negative.RemainingSeconds) || negative.DeadlineSeconds < 0.0 || negative.DeadlineSeconds > 51840.0 || negative.RemainingSeconds < 0.0 || negative.RemainingSeconds > negative.DeadlineSeconds || negative.Timed != negative.Id.StartsWith("timed_", StringComparison.Ordinal) || (negative.Timed && !State.ActiveRules.TimedNegativesEnabled) || (!negative.Timed && (negative.DeadlineSeconds != 0.0 || negative.RemainingSeconds != 0.0)) || (negative.Timed && negative.DeadlineSeconds <= 0.0) || (negative.Timed && Math.Abs(negative.DeadlineSeconds - State.NegativeDaySeconds * State.ClosingTimeFactor * 0.6) > 1E-09) || (negative.Completed && (negative.Count < negative.Required || (negative.Completer == 0 && !negative.WaivedCompletion) || (negative.Completer != 0 && !Contains(negative.Roster, negative.Completer)))) || (negative.WaivedCompletion && (!negative.Completed || negative.Completer != 0 || (negative.Timed ? negative.Id.Substring(6) : negative.Id) != "participation")) || (!negative.Completed && negative.Completer != 0) || (State.NegativesResolved && !negative.Completed && !negative.Failed)) { throw new ArgumentException("Invalid negative-task progress or deadline."); } HashSet hashSet3 = new HashSet(); ulong[] roster = negative.Roster; foreach (ulong num4 in roster) { if (num4 == 0 || !hashSet3.Add(num4)) { throw new ArgumentException("Invalid starting negative-task roster."); } } HashSet hashSet4 = new HashSet(); foreach (ulong participant in negative.Participants) { if (!hashSet3.Contains(participant) || !hashSet4.Add(participant)) { throw new ArgumentException("Invalid negative-task participants."); } } HashSet hashSet5 = new HashSet(); foreach (int game in negative.Games) { if (!Contains(State.AvailableGames, game) || !hashSet5.Add(game)) { throw new ArgumentException("Invalid negative-task games."); } } string text = (negative.Timed ? negative.Id.Substring(6) : negative.Id); int num5 = text switch { "six_plays" => 6, "variety" => 2, "participation" => (negative.Roster.Length == 1) ? 3 : negative.Roster.Length, _ => 1, }; if (negative.Required != num5 || negative.ProfitTarget != ((text == "net_profit") ? 0.03 : 0.0) || (negative.Count >= negative.Required && !negative.Completed) || (negative.Timed && negative.RemainingSeconds == 0.0 && !negative.Completed && !negative.Failed) || (text == "participation" && negative.Roster.Length > 1 && negative.Count != negative.Participants.Count) || (text == "variety" && negative.Count != Math.Min(negative.Required, negative.Games.Count))) { throw new ArgumentException("Negative-task counters do not match their goal."); } if (!string.IsNullOrEmpty(negative.PenaltyId) && (!negative.Failed || !State.RewardIds.Contains(negative.PenaltyId))) { throw new ArgumentException("Invalid negative-task penalty reference."); } num3 += negative.RiskRatio; } if (num3 > num + 1E-09) { throw new ArgumentException("Negative tasks exceed their matched daily risk cap."); } HashSet hashSet6 = new HashSet(); foreach (Cooldown negativeCooldown in State.NegativeCooldowns) { if (negativeCooldown == null || Find(NegativeModifiers, negativeCooldown.Id) == null || !hashSet6.Add(negativeCooldown.Id) || negativeCooldown.Round < 1 || negativeCooldown.Round > State.Round) { throw new ArgumentException("Invalid negative-task cooldown."); } } HashSet hashSet7 = new HashSet(); double num6 = 0.0; double num7 = 0.0; double num8 = 0.0; foreach (Reward pendingPenaltyReward in State.PendingPenaltyRewards) { ValidatePenaltyReward(pendingPenaltyReward, hashSet7, receipt: false); if (pendingPenaltyReward.Round != State.Round || pendingPenaltyReward.Day != State.Day || State.AppliedRewardIds.Contains(pendingPenaltyReward.Id)) { throw new ArgumentException("Stale or already applied pending penalty."); } num6 += pendingPenaltyReward.Amount; num8 += pendingPenaltyReward.ReservedAmount; } foreach (Reward penaltyReceipt in State.PenaltyReceipts) { ValidatePenaltyReward(penaltyReceipt, hashSet7, receipt: true); if (penaltyReceipt.Round == State.Round && penaltyReceipt.Day == State.Day) { num7 += penaltyReceipt.Amount; num8 += penaltyReceipt.ReservedAmount; } } if (num6 + State.PenaltyDelivered > State.PenaltyReserved + 1E-09 || Math.Abs(num7 - State.PenaltyDelivered) > 1E-09 || Math.Abs(num8 - State.PenaltyReserved) > 1E-09) { throw new ArgumentException("Penalty delivery exceeds its reserved loss."); } foreach (NegativeProgress negative2 in State.Negatives) { if (string.IsNullOrEmpty(negative2.PenaltyId)) { if (negative2.Failed && negative2.RiskRatio > 1E-12) { throw new ArgumentException("Failed negative task lacks its current-day loss receipt."); } } else if (!hashSet7.Contains(negative2.PenaltyId)) { throw new ArgumentException("Negative task references an unknown loss receipt."); } } } private void ValidatePenaltyReward(Reward reward, HashSet ids, bool receipt) { if (reward != null && !string.IsNullOrEmpty(reward.Id) && ids.Add(reward.Id) && State.RewardIds.Contains(reward.Id) && reward.Kind == RewardKind.PenaltyCash && reward.Player == 0 && reward.Actor == 0 && reward.Round >= 1 && reward.Round <= State.Round && reward.Day >= 0 && reward.Day <= State.Day && Finite(reward.Amount) && Finite(reward.ReservedAmount) && !(reward.ReservedAmount <= 0.0) && !(reward.ReservedAmount > 1.5) && !(reward.Amount < 0.0) && !(reward.Amount > reward.ReservedAmount + 1E-09) && (receipt || reward.Amount == reward.ReservedAmount) && (!receipt || reward.CashBound)) { if (reward.CashBound) { bool num; if (reward.Amount != 0.0) { num = !ValidBoundCash(reward); } else { if (reward.CashMantissa != 0.0) { goto IL_01a9; } num = reward.CashExponent != 0; } if (num) { goto IL_01a9; } } if (reward.CashBound || (reward.CashMantissa == 0.0 && reward.CashExponent == 0)) { return; } } goto IL_01a9; IL_01a9: throw new ArgumentException("Invalid penalty payment or receipt."); } private void UpdateProgress() { if (State.ContractId.Length == 0) { State.ContractProgress = ""; return; } if (State.ContractCompleted) { State.ContractProgress = "Completed"; return; } switch (State.ContractId) { case "casino_tour": State.ContractProgress = GameCount(wins: false) + "/" + Math.Min(3, State.AvailableGames.Length) + " game types"; break; case "everybody_in": State.ContractProgress = CrewText(wins: false); break; case "profit_ladder": State.ContractProgress = "Profit checkpoints: " + Math.Min(3, (int)Math.Floor(Math.Max(0.0, State.NativeProfit) / (State.ContractTarget / 3.0))) + "/3; " + Percent(State.NativeProfit) + "/" + Percent(State.ContractTarget) + " of the cash target"; break; case "recovery_crew": State.ContractProgress = (State.ComebackCompleted ? "Recovery complete" : (State.HadSetback ? "Win back 10% of the cash target from the lowest point" : "Waiting for a cash drop")); break; case "budget_night": State.ContractProgress = "Shop: " + Percent(State.Purchases) + "/35% of starting tickets; finish the day"; break; case "small_stakes": State.ContractProgress = "Small-bet profit: " + Percent(State.SmallStakeProfit) + "/" + Percent(State.ContractTarget * 0.5) + " of the cash target"; break; case "high_roller": State.ContractProgress = Math.Min(3, State.HighRollerPlays) + "/3 larger bets finished"; break; case "team_streak": State.ContractProgress = Math.Min(3, State.BestDayStreak) + "/3 wins in a row"; break; case "clean_run": State.ContractProgress = (State.RescueUsed ? "Rebate or rescue used; missed today" : "Finish the day without rebates or rescues"); break; case "featured_game": State.ContractProgress = "Game " + State.ContractFeaturedGame + ": " + Percent(FeaturedProfit()) + "/" + Percent(State.ContractTarget * 0.5); break; case "strong_finish": State.ContractProgress = "Casino profit: " + Percent(State.NativeProfit) + "/" + Percent(1.0 + State.ContractTarget * 0.5) + " of the cash target"; break; case "balanced_crew": State.ContractProgress = CrewText(wins: true); break; } } private string CrewText(bool wins) { if (State.Players.Length == 1) { PlayerProgress playerProgress = FindPlayer(State.Players[0]); if (!wins) { return Math.Min(3, playerProgress.Plays) + "/3 solo games"; } return Math.Min(2, playerProgress.Wins) + "/2 solo wins"; } int num = 0; ulong[] players = State.Players; foreach (ulong id in players) { PlayerProgress playerProgress2 = FindPlayer(id); if (wins ? (playerProgress2.Wins > 0) : (playerProgress2.Plays > 0)) { num++; } } return num + "/" + (wins ? Math.Min(2, State.Players.Length) : State.Players.Length) + " crew participants"; } private void SyncDescriptions() { State.ModifierNames.Clear(); State.ModifierDescriptions.Clear(); foreach (string modifierId in State.ModifierIds) { Definition definition = Find(Modifiers, modifierId); if (definition != null) { State.ModifierNames.Add(definition.Name); State.ModifierDescriptions.Add(definition.Description); } } State.PerkNames.Clear(); State.PerkDescriptions.Clear(); foreach (string perkId in State.PerkIds) { Definition definition2 = Find(Perks, perkId); if (definition2 != null) { State.PerkNames.Add(definition2.Name); State.PerkDescriptions.Add(definition2.Description); } } } private void RefreshPreview() { State.NextModifierPreview = ""; if (!PerkActive("scout")) { return; } foreach (string item in State.ModifierDeck) { if (EligibleModifier(item) && State.Round + 1 - LastModifier(item) > 2) { State.NextModifierPreview = Find(Modifiers, item).Name; break; } } if (State.NextModifierPreview.Length == 0) { State.NextModifierPreview = "The pool reshuffles at the next round."; } } private int Next(int count) { if (count <= 1) { return 0; } uint randomState = State.RandomState; randomState ^= randomState << 13; randomState ^= randomState >> 17; randomState ^= randomState << 5; State.RandomState = ((randomState == 0) ? 1831565813u : randomState); return (int)(State.RandomState % (uint)count); } private void Shuffle(List list) { for (int num = list.Count - 1; num > 0; num--) { int index = Next(num + 1); string value = list[num]; list[num] = list[index]; list[index] = value; } } private static Definition Find(Definition[] catalog, string id) { foreach (Definition definition in catalog) { if (definition.Id == id) { return definition; } } return null; } private static bool KnownIds(List ids, Definition[] catalog) { List list = new List(); foreach (string id in ids) { if (Find(catalog, id) == null || list.Contains(id)) { return false; } list.Add(id); } return true; } private static void Discover(List list, string id) { if (!list.Contains(id)) { list.Add(id); } } private static void Trim(List list, int maximum) { if (list.Count > maximum) { list.RemoveRange(0, list.Count - maximum); } } private static bool Finite(double n) { if (!double.IsNaN(n)) { return !double.IsInfinity(n); } return false; } private static string Percent(double n) { return (n * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "%"; } private static bool Contains(int[] list, int n) { return Array.IndexOf(list, n) >= 0; } private static bool Contains(ulong[] list, ulong n) { return Array.IndexOf(list, n) >= 0; } private static int[] Unique(int[] source) { List list = new List(); if (source != null) { foreach (int num in source) { if (num >= 0 && !list.Contains(num)) { list.Add(num); } } } list.Sort(); return list.ToArray(); } private static ulong[] Unique(ulong[] source) { List list = new List(); if (source != null) { foreach (ulong num in source) { if (num != 0 && !list.Contains(num)) { list.Add(num); } } } list.Sort(); return list.ToArray(); } private static Rules Normalize(Rules source) { source = source ?? new Rules(); Rules rules = new Rules(); rules.Enabled = source.Enabled; rules.Intensity = Math.Max(1, Math.Min(10, source.Intensity)); rules.JackpotPercent = (Finite(source.JackpotPercent) ? Math.Max(0.0, Math.Min(25.0, source.JackpotPercent)) : 3.0); rules.ContractsEnabled = source.ContractsEnabled; rules.ClosingTimeEnabled = source.ClosingTimeEnabled; rules.MaxBonusQuotaFraction = (Finite(source.MaxBonusQuotaFraction) ? Math.Max(0.0, Math.Min(1.0, source.MaxBonusQuotaFraction)) : 0.35); rules.Seed = source.Seed; rules.DisabledModifiers = source.DisabledModifiers ?? ""; rules.PurchasesSupported = source.PurchasesSupported; rules.MaxTicketsPerDay = Math.Max(0, Math.Min(10, source.MaxTicketsPerDay)); rules.ChapterRounds = Math.Max(2, Math.Min(12, source.ChapterRounds)); rules.NegativesEnabled = source.NegativesEnabled; rules.NegativeDayPercent = (Finite(source.NegativeDayPercent) ? Math.Max(0.0, Math.Min(100.0, source.NegativeDayPercent)) : 50.0); rules.TimedNegativesEnabled = source.TimedNegativesEnabled; rules.PenaltySeverity = Math.Max(1, Math.Min(3, source.PenaltySeverity)); return rules; } private void RepairLists() { if (State.Players == null) { State.Players = new ulong[0]; } if (State.AvailableGames == null) { State.AvailableGames = new int[0]; } if (State.PlayerProgress == null) { State.PlayerProgress = new List(); } if (State.GameProgress == null) { State.GameProgress = new List(); } if (State.ModifierIds == null) { State.ModifierIds = new List(); } if (State.ModifierNames == null) { State.ModifierNames = new List(); } if (State.ModifierDescriptions == null) { State.ModifierDescriptions = new List(); } if (State.ModifierDeck == null) { State.ModifierDeck = new List(); } if (State.ContractDeck == null) { State.ContractDeck = new List(); } if (State.ModifierCooldowns == null) { State.ModifierCooldowns = new List(); } if (State.PerkIds == null) { State.PerkIds = new List(); } if (State.PerkNames == null) { State.PerkNames = new List(); } if (State.PerkDescriptions == null) { State.PerkDescriptions = new List(); } if (State.Choices == null) { State.Choices = new List(); } if (State.RewardIds == null) { State.RewardIds = new List(); } if (State.AppliedRewardIds == null) { State.AppliedRewardIds = new List(); } if (State.PendingCashRewards == null) { State.PendingCashRewards = new List(); } if (State.KilledNpcKeys == null) { State.KilledNpcKeys = new List(); } if (State.CashReceipts == null) { State.CashReceipts = new List(); } if (State.Negatives == null) { State.Negatives = new List(); } if (State.NegativeDeck == null) { State.NegativeDeck = new List(); } if (State.NegativeCooldowns == null) { State.NegativeCooldowns = new List(); } if (State.PendingPenaltyRewards == null) { State.PendingPenaltyRewards = new List(); } if (State.PenaltyReceipts == null) { State.PenaltyReceipts = new List(); } if (State.DiscoveredModifiers == null) { State.DiscoveredModifiers = new List(); } if (State.DiscoveredJackpots == null) { State.DiscoveredJackpots = new List(); } if (State.CompletedContractIds == null) { State.CompletedContractIds = new List(); } if (State.Medals == null) { State.Medals = new List(); } if (State.RecentEvents == null) { State.RecentEvents = new List(); } if (State.ContractId == null) { State.ContractId = ""; } if (State.NextModifierPreview == null) { State.NextModifierPreview = ""; } ulong[] players = State.Players; foreach (ulong num in players) { bool flag = false; foreach (PlayerProgress item in State.PlayerProgress) { if (item.Id == num) { flag = true; } } if (!flag) { State.PlayerProgress.Add(new PlayerProgress { Id = num }); } } int[] availableGames = State.AvailableGames; foreach (int num2 in availableGames) { bool flag2 = false; foreach (GameProgress item2 in State.GameProgress) { if (item2.Id == num2) { flag2 = true; } } if (!flag2) { State.GameProgress.Add(new GameProgress { Id = num2 }); } } } } } namespace ChaosNight { public sealed class DayEndOverlayState { public bool Armed; public bool Allowed; public bool SameScene; public bool SameManager; public bool SameDay; public bool PlayingGame; public bool Expired; public bool Transitioning; public bool PlayerPlaying; public bool AnyFeedbackPlaying; public bool LoopOrScriptPause; public bool ExactFinalStage; public bool ExactTarget; public bool NativeHierarchy; public bool Forward; public bool Consumed; public double Duration; public double Elapsed; public double ScaledElapsed; public double TimeScale; public double Alpha; } public static class DayEndOverlayPolicy { public static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } public static bool CanRecover(DayEndOverlayState state) { if (state != null && state.Armed && state.Allowed && state.SameScene && state.SameManager && state.SameDay && state.PlayingGame && state.Expired && !state.Transitioning && !state.PlayerPlaying && !state.AnyFeedbackPlaying && !state.LoopOrScriptPause && state.ExactFinalStage && state.ExactTarget && state.NativeHierarchy && state.Forward && !state.Consumed && Finite(state.Duration) && state.Duration > 0.0 && Finite(state.Elapsed) && state.Elapsed >= Math.Max(10.0, state.Duration + 5.0) && Finite(state.ScaledElapsed) && state.ScaledElapsed >= state.Duration + 2.0 && Finite(state.TimeScale) && state.TimeScale > 0.0 && Finite(state.Alpha) && state.Alpha > 0.001) { return state.Alpha <= 1.0; } return false; } } public sealed class DayEndOverlayReceipt { private object player; private int playCount; public bool Armed { get; private set; } public bool Consumed { get; private set; } public bool Observe(object observedPlayer, int observedPlayCount) { if (observedPlayer == null || observedPlayCount <= 0) { return false; } if (object.ReferenceEquals(player, observedPlayer) && observedPlayCount == playCount) { return false; } player = observedPlayer; playCount = observedPlayCount; Armed = true; Consumed = false; return true; } public bool Matches(object currentPlayer, int currentPlayCount) { if (Armed && object.ReferenceEquals(player, currentPlayer)) { return playCount == currentPlayCount; } return false; } public bool TryConsume(DayEndOverlayState state) { if (!Armed || Consumed || !DayEndOverlayPolicy.CanRecover(state)) { return false; } Consumed = true; Armed = false; return true; } public void Cancel() { Armed = false; } } internal sealed class ChaosDayEndOverlay : IDisposable { [HarmonyPatch(typeof(GameManager), "UserCode_RpcPlayDayEndFeedback")] private static class NativeDayEndFeedbackObservedPatch { private static void Postfix(GameManager __instance) { active?.ObserveNativeFeedback(__instance); } } private static ChaosDayEndOverlay active; private static readonly FieldInfo FeedbackField = AccessTools.Field(typeof(GameManager), "dayEndFb"); private static readonly FieldInfo InstanceField = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly FieldInfo ExpiredField = AccessTools.Field(typeof(GameManager), "_isTimeOver"); private static readonly FieldInfo TransitionField = AccessTools.Field(typeof(GameManager), "_isTransitioning"); private readonly Func allowed; private readonly Action log; private readonly DayEndOverlayReceipt receipt = new DayEndOverlayReceipt(); private GameManager manager; private MMF_Player player; private MMF_CanvasGroup finalStage; private CanvasGroup target; private MMF_Feedback[] nativeFeedbacks; private int sceneHandle; private int managerSceneHandle; private int expiredDay; private bool serverReceipt; private float observedAt; private float observedScaledAt; private float observedDuration; internal ChaosDayEndOverlay(Func allowed, Action log) { this.allowed = allowed ?? ((Func)(() => false)); this.log = log; active = this; } private static bool ExactFields() { if (FeedbackField != null && FeedbackField.FieldType == typeof(MMF_Player) && InstanceField != null && InstanceField.FieldType == typeof(GameManager) && ExpiredField != null && ExpiredField.FieldType == typeof(bool) && TransitionField != null) { return TransitionField.FieldType == typeof(bool); } return false; } private bool HasExpiryProof(GameManager owner, bool isServer) { if (!isServer) { if (NetworkClient.active) { return ((NetworkBehaviour)owner).isClient; } return false; } return (bool)ExpiredField.GetValue(owner); } private static bool ExactHierarchy(GameManager owner, MMF_Player feedback, CanvasGroup canvas) { //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)owner == (Object)null || (Object)(object)feedback == (Object)null || (Object)(object)canvas == (Object)null) { return false; } if (object.ReferenceEquals(((Component)feedback).transform.parent, ((Component)owner).transform) && object.ReferenceEquals(((Component)canvas).transform.parent, ((Component)feedback).transform)) { Scene scene = ((Component)owner).gameObject.scene; SceneHandle handle = ((Scene)(ref scene)).handle; Scene scene2 = ((Component)feedback).gameObject.scene; if (handle == ((Scene)(ref scene2)).handle) { Scene scene3 = ((Component)feedback).gameObject.scene; SceneHandle handle2 = ((Scene)(ref scene3)).handle; Scene scene4 = ((Component)canvas).gameObject.scene; return handle2 == ((Scene)(ref scene4)).handle; } } return false; } private static MMF_CanvasGroup FinalActiveStage(MMF_Player feedback) { if ((Object)(object)feedback == (Object)null || feedback.FeedbacksList == null) { return null; } for (int num = feedback.FeedbacksList.Count - 1; num >= 0; num--) { MMF_Feedback val = feedback.FeedbacksList[num]; if (val != null && val.Active) { return (MMF_CanvasGroup)(object)((val is MMF_CanvasGroup) ? val : null); } } return null; } private static bool ValidStage(MMF_Player feedback, MMF_CanvasGroup stage) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (stage != null && object.ReferenceEquals(((MMF_Feedback)stage).Owner, feedback) && ((MMF_Feedback)stage).Active && ((MMF_Feedback)stage).NormalPlayDirection && (int)((MMF_FeedbackBase)stage).Mode == 0 && !((MMF_FeedbackBase)stage).RelativeValues && stage.RemapOne == 0f) { return (Object)(object)stage.TargetCanvasGroup != (Object)null; } return false; } private static bool LoopOrPause(MMF_Player feedback) { if (((MMFeedbacks)feedback).ContainsLoop || ((MMFeedbacks)feedback).InScriptDrivenPause || feedback.FeedbacksList == null) { return true; } foreach (MMF_Feedback feedbacks in feedback.FeedbacksList) { if (feedbacks != null && (feedbacks.ScriptDrivenPause || feedbacks.LooperPause || feedbacks.LooperStart || feedbacks.Timing == null || feedbacks.Timing.RepeatForever || feedbacks.Timing.NumberOfRepeats != 0)) { return true; } } return false; } private void ObserveNativeFeedback(GameManager owner) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_0050: Invalid comparison between Unknown and I4 //IL_00a1: Unknown result type (might be due to invalid IL or missing references) //IL_0155: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_0170: Unknown result type (might be due to invalid IL or missing references) //IL_0174: Unknown result type (might be due to invalid IL or missing references) try { Scene activeScene = SceneManager.GetActiveScene(); if (!ExactFields() || !allowed() || (Object)(object)owner == (Object)null || ((Scene)(ref activeScene)).name != "CasinoScene" || !object.ReferenceEquals(InstanceField.GetValue(null), owner) || (int)owner.state != 1 || (bool)TransitionField.GetValue(owner)) { return; } bool isServer = ((NetworkBehaviour)owner).isServer; if (!HasExpiryProof(owner, isServer)) { return; } object? value = FeedbackField.GetValue(owner); MMF_Player val = (MMF_Player)((value is MMF_Player) ? value : null); MMF_CanvasGroup val2 = FinalActiveStage(val); if (!((Object)(object)val == (Object)null) && (int)((MMFeedbacks)val).Direction == 0 && !LoopOrPause(val) && ValidStage(val, val2) && ExactHierarchy(owner, val, val2.TargetCanvasGroup)) { float totalDuration = ((MMFeedbacks)val).TotalDuration; if (DayEndOverlayPolicy.Finite(totalDuration) && !(totalDuration <= 0f) && DayEndOverlayPolicy.Finite(Time.unscaledTime) && DayEndOverlayPolicy.Finite(Time.time) && receipt.Observe(val, val.PlayCount)) { manager = owner; player = val; finalStage = val2; target = val2.TargetCanvasGroup; nativeFeedbacks = val.FeedbacksList.ToArray(); sceneHandle = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle); Scene scene = ((Component)owner).gameObject.scene; managerSceneHandle = SceneHandle.op_Implicit(((Scene)(ref scene)).handle); expiredDay = owner.daysPassed; serverReceipt = isServer; observedAt = Time.unscaledTime; observedScaledAt = Time.time; observedDuration = totalDuration; } } } catch (Exception ex) { Clear(); Report("Day-end overlay observation held: " + ex.GetBaseException().Message); } } internal void Tick() { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_0084: 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_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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_010a: Unknown result type (might be due to invalid IL or missing references) //IL_02a2: Unknown result type (might be due to invalid IL or missing references) if (!receipt.Armed) { return; } try { Scene activeScene = SceneManager.GetActiveScene(); if (ExactFields() && allowed() && !((Object)(object)manager == (Object)null) && !((Object)(object)player == (Object)null) && !((Object)(object)target == (Object)null) && !(((Scene)(ref activeScene)).name != "CasinoScene") && !(((Scene)(ref activeScene)).handle != SceneHandle.op_Implicit(sceneHandle)) && object.ReferenceEquals(InstanceField.GetValue(null), manager) && manager.daysPassed == expiredDay && (int)manager.state == 1 && ((NetworkBehaviour)manager).isServer == serverReceipt) { Scene scene = ((Component)manager).gameObject.scene; if (!(((Scene)(ref scene)).handle != SceneHandle.op_Implicit(managerSceneHandle)) && !(bool)TransitionField.GetValue(manager) && HasExpiryProof(manager, serverReceipt) && receipt.Matches(player, player.PlayCount) && object.ReferenceEquals(FeedbackField.GetValue(manager), player)) { if (player.FeedbacksList == null || nativeFeedbacks == null || player.FeedbacksList.Count != nativeFeedbacks.Length) { Clear(); return; } bool anyFeedbackPlaying = false; for (int i = 0; i < nativeFeedbacks.Length; i++) { MMF_Feedback val = player.FeedbacksList[i]; if (!object.ReferenceEquals(val, nativeFeedbacks[i])) { Clear(); return; } if (val != null && val.IsPlaying) { anyFeedbackPlaying = true; } } float totalDuration = ((MMFeedbacks)player).TotalDuration; if (!DayEndOverlayPolicy.Finite(totalDuration) || totalDuration <= 0f || totalDuration != observedDuration || !object.ReferenceEquals(FinalActiveStage(player), finalStage) || !ValidStage(player, finalStage) || !object.ReferenceEquals(finalStage.TargetCanvasGroup, target) || !ExactHierarchy(manager, player, target) || (int)((MMFeedbacks)player).Direction != 0 || LoopOrPause(player)) { Clear(); return; } DayEndOverlayState dayEndOverlayState = new DayEndOverlayState(); dayEndOverlayState.Armed = receipt.Armed; dayEndOverlayState.Allowed = true; dayEndOverlayState.SameScene = true; dayEndOverlayState.SameManager = true; dayEndOverlayState.SameDay = true; dayEndOverlayState.PlayingGame = true; dayEndOverlayState.Expired = true; dayEndOverlayState.Transitioning = false; dayEndOverlayState.PlayerPlaying = ((MMFeedbacks)player).IsPlaying; dayEndOverlayState.AnyFeedbackPlaying = anyFeedbackPlaying; dayEndOverlayState.LoopOrScriptPause = false; dayEndOverlayState.ExactFinalStage = true; dayEndOverlayState.ExactTarget = true; dayEndOverlayState.NativeHierarchy = true; dayEndOverlayState.Forward = true; dayEndOverlayState.Consumed = receipt.Consumed; dayEndOverlayState.Duration = totalDuration; dayEndOverlayState.Elapsed = (double)Time.unscaledTime - (double)observedAt; dayEndOverlayState.ScaledElapsed = (double)Time.time - (double)observedScaledAt; dayEndOverlayState.TimeScale = Time.timeScale; dayEndOverlayState.Alpha = target.alpha; DayEndOverlayState dayEndOverlayState2 = dayEndOverlayState; if (receipt.TryConsume(dayEndOverlayState2)) { target.alpha = 0f; Report("Cleared the exact native day-end overlay after completed feedback left stale alpha; day=" + expiredDay + ", scene=" + sceneHandle + ", previousAlpha=" + dayEndOverlayState2.Alpha + "."); Clear(); } return; } } Clear(); } catch (Exception ex) { Clear(); Report("Day-end overlay recovery held: " + ex.GetBaseException().Message); } } private void Report(string message) { if (log != null) { try { log(message); } catch { } } } private void Clear() { receipt.Cancel(); manager = null; player = null; finalStage = null; target = null; nativeFeedbacks = null; } public void Dispose() { Clear(); if (object.ReferenceEquals(active, this)) { active = null; } } } public sealed class ChaosGunReload : IDisposable { private sealed class HostGun { public QuotaGun Gun; public GunMagazine Magazine; public string Slot; public NetworkConnectionToClient Connection; public int Sequence; } private sealed class PeerSequence { public NetworkConnectionToClient Connection; public int Sequence; public double NextQuery; } private static class GunSpawnPatch { private static void Postfix(Item __instance) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Expected O, but got Unknown if (current == null || !NetworkServer.active || !(__instance is QuotaGun)) { return; } try { current.State((QuotaGun)__instance); } catch (Exception error) { current.Fail(error); } } } private sealed class CommandScope : IDisposable { public QuotaGun Gun; public NetworkConnectionToClient Connection; public bool Allowed; private readonly CommandScope previous; private bool disposed; public CommandScope(QuotaGun gun, NetworkConnectionToClient connection) { Gun = gun; Connection = connection; previous = command; command = this; } public void Dispose() { if (!disposed) { disposed = true; command = previous; } } } private static class LocalShootPatch { [HarmonyPriority(800)] private static bool Prefix(QuotaGun __instance) { if (current != null) { return current.Shoot(__instance); } return true; } } private static class LocalUsePatch { [HarmonyPriority(800)] private static bool Prefix(QuotaGun __instance, bool __0) { //IL_0054: Unknown result type (might be due to invalid IL or missing references) if (!__0 || current == null) { return true; } if (!current.Enabled || (Object)(object)current.localGun != (Object)(object)__instance || !current.synced || current.pendingReload > 0 || current.reloadUntil > Now || (int)InputEvents.ActiveLayer != 0 || current.overlayOpen()) { return false; } if (current.ammo <= 0) { current.Reload(); return false; } return true; } } private static class NativeInvokerPatch { internal static IEnumerable TargetMethods() { try { string[] array = new string[5] { "InvokeUserCode_CmdShootPlayer__PlayerController__Vector3__Vector3", "InvokeUserCode_CmdShootNpc__NPC__Vector3__Vector3", "InvokeUserCode_CmdShootItem__Item__Vector3__Vector3", "InvokeUserCode_CmdPlayShootEffects__Vector3", "InvokeUserCode_CmdPlayHitEffects__Boolean__Vector3" }; foreach (string name in array) { yield return AccessTools.Method(typeof(QuotaGun), name, (Type[])null, (Type[])null); } } finally { } } [HarmonyPriority(800)] private static void Prefix(NetworkBehaviour __0, NetworkConnectionToClient __2, out CommandScope __state) { __state = new CommandScope((QuotaGun)(object)((__0 is QuotaGun) ? __0 : null), __2); } private static void Postfix(CommandScope __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, CommandScope __state) { __state?.Dispose(); return __exception; } } private static class NativeDamagePatch { internal static IEnumerable TargetMethods() { try { string[] array = new string[3] { "UserCode_CmdShootPlayer__PlayerController__Vector3__Vector3", "UserCode_CmdShootNpc__NPC__Vector3__Vector3", "UserCode_CmdShootItem__Item__Vector3__Vector3" }; foreach (string name in array) { yield return AccessTools.Method(typeof(QuotaGun), name, (Type[])null, (Type[])null); } } finally { } } [HarmonyPriority(800)] private static bool Prefix(QuotaGun __instance, object __0, Vector3 __1, Vector3 __2) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) if (current != null) { return current.Authorize(__instance, 1, __0, __1, __2); } return true; } } private static class NativeShootEffectsPatch { [HarmonyPriority(800)] private static bool Prefix(QuotaGun __instance, Vector3 __0) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (current != null) { return current.Authorize(__instance, 4, null, Vector3.forward, __0); } return true; } } private static class NativeHitEffectsPatch { [HarmonyPriority(800)] private static bool Prefix(QuotaGun __instance, Vector3 __1) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) if (current != null) { return current.Authorize(__instance, 2, null, Vector3.forward, __1); } return true; } } private static class GunDestroyPatch { private static void Prefix(Item __instance) { if (current != null && __instance is QuotaGun) { if (current.guns.TryGetValue(((NetworkBehaviour)__instance).netId, out var value) && object.ReferenceEquals(value.Gun, __instance)) { current.guns.Remove(((NetworkBehaviour)__instance).netId); } if ((Object)(object)current.localGun == (Object)(object)__instance) { current.RestorePose(); current.localGun = null; current.display.Reset((current.sequence == int.MaxValue) ? current.sequence : (current.sequence + 1)); } } } } public const string HarmonyOwner = "co.oasistechnologies.gwyf.chaosnight.gun-reload"; private static ChaosGunReload current; private readonly Harmony reloadHarmony; private readonly ChaosNetwork network; private readonly Func allowed; private readonly Func reducedMotion; private readonly Func overlayOpen; private readonly Func saveIdentity; private readonly Action log; private readonly GunAmmoJournal journal; private readonly Dictionary guns = new Dictionary(); private readonly Dictionary peers = new Dictionary(); private readonly List remove = new List(); private readonly GunClientMagazine display = new GunClientMagazine(); private QuotaGun localGun; private int sequence; private int pendingReload; private string selectedSave; private bool faulted; private bool disposed; private bool wasServer; private double reloadUntil; private double reloadBegan; private double nextQuery; private Transform pose; private Transform poseParent; private Vector3 posePosition; private Quaternion poseRotation; private GUIStyle titleStyle; private GUIStyle hintStyle; private static readonly FieldInfo NativeCooldown = AccessTools.Field(typeof(QuotaGun), "shootCooldown"); [ThreadStatic] private static CommandScope command; private int ammo => display.Ammo; private bool synced => display.Synced; private bool Enabled { get { if (!disposed && !faulted) { return allowed(); } return false; } } private static double Now => Time.unscaledTimeAsDouble; internal ChaosGunReload(ChaosNetwork network, Func allowed, Func reducedMotion, Func overlayOpen, Func saveIdentity, string configRoot, Action log) { //IL_00b4: Unknown result type (might be due to invalid IL or missing references) //IL_00be: Expected O, but got Unknown this.network = network; this.allowed = allowed; this.reducedMotion = reducedMotion; this.overlayOpen = overlayOpen; this.saveIdentity = saveIdentity; this.log = log; journal = new GunAmmoJournal(configRoot); current = this; network.GunRequested += Request; network.GunStateReceived += Receive; if (NativeCooldown == null) { throw new InvalidOperationException("Native gun cooldown unavailable."); } reloadHarmony = new Harmony("co.oasistechnologies.gwyf.chaosnight.gun-reload"); InitializePatches(); } private QuotaGun Held() { NetworkIdentity localPlayer = NetworkClient.localPlayer; PlayerInventory val = (((Object)(object)localPlayer == (Object)null) ? null : ((Component)localPlayer).GetComponent()); QuotaGun val2 = (QuotaGun)(((Object)(object)val == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if (!((Object)(object)val2 != (Object)null) || !((NetworkBehaviour)val).isLocalPlayer || !((Object)(object)((Item)val2).NetworkHolder == (Object)(object)val) || ((Item)val2).isInPocket || !((Component)val2).gameObject.activeInHierarchy) { return null; } return val2; } public void Tick() { //IL_0158: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } try { if (wasServer && !NetworkServer.active) { foreach (HostGun value in guns.Values) { value.Magazine.Bind(0u); } guns.Clear(); peers.Clear(); } wasServer = NetworkServer.active; if (NetworkServer.active) { HostTick(); } QuotaGun val = (Enabled ? Held() : null); if ((Object)(object)localGun != (Object)(object)val) { RestorePose(); localGun = val; display.Reset((sequence == int.MaxValue) ? sequence : (sequence + 1)); reloadUntil = (reloadBegan = 0.0); pendingReload = 0; nextQuery = 0.0; } if (!((Object)(object)localGun == (Object)null)) { double now = Now; if (now >= nextQuery) { nextQuery = now + 0.5; Send(0); } if (synced && reloadUntil <= now && (int)InputEvents.ActiveLayer == 0 && !overlayOpen() && display.PendingCount == 0 && !InputEvents.IsEmoteWheelPressed && (ammo == 0 || (Keyboard.current != null && ((ButtonControl)Keyboard.current.fKey).wasPressedThisFrame))) { Reload(); } } } catch (Exception error) { Fail(error); } } private void HostTick() { remove.Clear(); double now = Now; if (Enabled && guns.Count > 0 && !SelectSave()) { return; } foreach (KeyValuePair gun2 in guns) { HostGun value = gun2.Value; QuotaGun gun = value.Gun; if ((Object)(object)gun == (Object)null || !NetworkServer.spawned.TryGetValue(gun2.Key, out var value2) || !object.ReferenceEquals(value2, ((NetworkBehaviour)gun).netIdentity)) { remove.Add(gun2.Key); continue; } NetworkConnectionToClient connection = (((Object)(object)((Item)gun).NetworkHolder == (Object)null) ? null : ((NetworkBehaviour)((Item)gun).NetworkHolder).connectionToClient); if (!Enabled || !ActorOwns(connection, gun, out var actor)) { value.Magazine.Bind(0u); continue; } value.Magazine.Bind(actor); if (value.Magazine.CompleteReload(actor, now)) { journal.Commit(value.Slot, value.Magazine.Ammo); Reply(value); } } foreach (uint item in remove) { guns.Remove(item); } List list = null; foreach (KeyValuePair peer in peers) { if (!network.GunPeerAllowed(peer.Value.Connection)) { if (list == null) { list = new List(); } list.Add(peer.Key); } } if (list == null) { return; } foreach (int item2 in list) { peers.Remove(item2); } } private bool ActorOwns(NetworkConnectionToClient connection, QuotaGun gun, out uint actor) { actor = 0u; if (connection == null || (Object)(object)gun == (Object)null || !((NetworkBehaviour)gun).isServer || !((NetworkConnection)connection).isAuthenticated || !((NetworkConnection)connection).isReady || !network.GunPeerAllowed(connection) || (Object)(object)((NetworkConnection)connection).identity == (Object)null || ((NetworkConnection)connection).identity.netId == 0 || !NetworkServer.connections.TryGetValue(connection.connectionId, out var value) || !object.ReferenceEquals(value, connection) || !object.ReferenceEquals(((NetworkConnection)connection).identity.connectionToClient, connection) || !NetworkServer.spawned.TryGetValue(((NetworkConnection)connection).identity.netId, out var value2) || !object.ReferenceEquals(value2, ((NetworkConnection)connection).identity) || ((NetworkBehaviour)gun).netId == 0 || !NetworkServer.spawned.TryGetValue(((NetworkBehaviour)gun).netId, out value2) || !object.ReferenceEquals(value2, ((NetworkBehaviour)gun).netIdentity)) { return false; } PlayerInventory component = ((Component)((NetworkConnection)connection).identity).GetComponent(); PlayerProfile component2 = ((Component)((NetworkConnection)connection).identity).GetComponent(); PlayerController component3 = ((Component)((NetworkConnection)connection).identity).GetComponent(); if ((Object)(object)component == (Object)null || (Object)(object)component2 == (Object)null || component2.steamId == 0 || (Object)(object)component3 == (Object)null || !component3.hasBody || component3.IsLocked || (Object)(object)component.holdingItem != (Object)(object)gun || (Object)(object)((Item)gun).NetworkHolder != (Object)(object)component || ((Item)gun).isInPocket || !((Component)gun).gameObject.activeInHierarchy) { return false; } actor = ((NetworkConnection)connection).identity.netId; return true; } private string Slot(QuotaGun gun, out int owned) { owned = 0; ItemManager instance = NetworkSingleton.Instance; if ((Object)(object)instance == (Object)null || instance.spawnedItemInstances == null || instance.currentItems == null || (Object)(object)((Item)gun).spawnableSo == (Object)null) { return null; } bool flag = false; foreach (ConsumableItem spawnedItemInstance in instance.spawnedItemInstances) { if (object.ReferenceEquals(spawnedItemInstance, gun)) { flag = true; break; } } owned = 0; foreach (SpawnableSO currentItem in instance.currentItems) { if ((Object)(object)currentItem == (Object)(object)((Item)gun).spawnableSo) { owned++; } } if (!flag || owned <= 0 || owned > 512) { return null; } for (int i = 0; i < owned; i++) { string text = ((Item)gun).spawnableSo.spawnableID + ":" + i; bool flag2 = false; foreach (HostGun value in guns.Values) { if ((Object)(object)value.Gun != (Object)null && value.Slot == text && !object.ReferenceEquals(value.Gun, gun)) { flag2 = true; break; } } if (!flag2) { return text; } } return null; } private bool SelectSave() { string text = saveIdentity(); if (string.IsNullOrEmpty(text)) { return false; } if (text == selectedSave) { return true; } journal.Select(text); foreach (HostGun value in guns.Values) { value.Magazine.Bind(0u); } guns.Clear(); selectedSave = text; display.Reset((sequence == int.MaxValue) ? sequence : (sequence + 1)); reloadUntil = 0.0; pendingReload = 0; return true; } private HostGun State(QuotaGun gun) { if (!SelectSave()) { return null; } if (guns.TryGetValue(((NetworkBehaviour)gun).netId, out var value) && object.ReferenceEquals(value.Gun, gun)) { return value; } if (guns.Count >= 512) { throw new InvalidOperationException("Magazine gun limit exceeded."); } int owned; string text = Slot(gun, out owned); if (text == null) { return null; } HostGun hostGun = new HostGun(); hostGun.Gun = gun; hostGun.Slot = text; hostGun.Magazine = new GunMagazine(journal.Read(text, owned)); value = hostGun; journal.Commit(text, value.Magazine.Ammo); guns[((NetworkBehaviour)gun).netId] = value; return value; } private void Request(NetworkConnectionToClient connection, ChaosNetwork.GunRequest request) { try { if (!Enabled || !NetworkServer.active || !network.GunPeerAllowed(connection)) { return; } if (!peers.TryGetValue(connection.connectionId, out var value) || !object.ReferenceEquals(value.Connection, connection)) { if (peers.Count >= 128) { return; } value = (peers[connection.connectionId] = new PeerSequence { Connection = connection }); } if (!GunMagazine.AcceptSequence(request.Sequence, value.Sequence)) { return; } value.Sequence = request.Sequence; if (!NetworkServer.spawned.TryGetValue(request.Gun, out var value2)) { return; } QuotaGun component = ((Component)value2).GetComponent(); if (!ActorOwns(connection, component, out var actor)) { return; } double now = Now; if (request.Action == 0 && now < value.NextQuery) { return; } value.NextQuery = now + 0.25; HostGun hostGun = State(component); if (hostGun == null) { return; } hostGun.Magazine.Bind(actor); hostGun.Connection = connection; hostGun.Sequence = request.Sequence; int acceptedShot = 0; if (request.Action == 1) { hostGun.Magazine.InvalidateShot(); float num = (float)NativeCooldown.GetValue(component); if (hostGun.Magazine.Reserve(actor, now, num)) { journal.Commit(hostGun.Slot, hostGun.Magazine.Ammo); acceptedShot = request.Sequence; } } else if (request.Action == 2) { hostGun.Magazine.BeginReload(actor, now); } Reply(hostGun, acceptedShot); } catch (Exception error) { Fail(error); } } private void Reply(HostGun state, int acceptedShot = 0) { if (state.Connection != null && state.Sequence > 0 && state.Magazine.Owner != 0) { network.ReplyGun(state.Connection, new ChaosNetwork.GunState { Gun = ((NetworkBehaviour)state.Gun).netId, Owner = state.Magazine.Owner, Sequence = state.Sequence, AcceptedShot = acceptedShot, Ammo = (byte)state.Magazine.Ammo, Reload = (state.Magazine.Reloading ? ((float)Math.Max(0.0, Math.Min(1.5, state.Magazine.ReloadUntil - Now))) : 0f) }); } } private int Send(byte action) { if ((Object)(object)localGun == (Object)null || sequence == int.MaxValue) { return 0; } int result = ++sequence; if (!network.RequestGun(((NetworkBehaviour)localGun).netId, result, action)) { return 0; } return result; } private void Receive(ChaosNetwork.GunState state) { if ((Object)(object)localGun == (Object)null || state.Gun != ((NetworkBehaviour)localGun).netId || (Object)(object)NetworkClient.localPlayer == (Object)null || state.Owner != NetworkClient.localPlayer.netId || !GunMagazine.AcceptReloadSnapshot(state.Sequence, pendingReload) || !display.Snapshot(state.Sequence, state.Ammo, state.AcceptedShot, Now)) { return; } pendingReload = 0; if (state.Reload > 0f) { if (reloadUntil <= Now) { reloadBegan = Now; } reloadUntil = Now + (double)state.Reload; } else if (display.PendingCount == 0) { reloadUntil = 0.0; } } private bool Shoot(QuotaGun gun) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || (Object)(object)localGun != (Object)(object)gun || (Object)(object)Held() != (Object)(object)gun || !synced || pendingReload > 0 || reloadUntil > Now || (int)InputEvents.ActiveLayer != 0 || overlayOpen()) { return false; } if (ammo <= 0) { Reload(); return false; } int num = sequence + 1; if (sequence == int.MaxValue || !display.Predict(num)) { return false; } if (Send(1) != num) { display.CancelPrediction(num); return false; } return true; } private void Reload() { if (Enabled && !((Object)(object)localGun == (Object)null) && synced && display.PendingCount <= 0 && ammo < 10 && pendingReload <= 0 && !(reloadUntil > Now)) { reloadBegan = Now; reloadUntil = Now + 1.5; pendingReload = sequence + 1; if (Send(2) == 0) { reloadUntil = 0.0; pendingReload = 0; } } } private bool Authorize(QuotaGun gun, int route, object target, Vector3 direction, Vector3 point) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) CommandScope commandScope = command; if (!Enabled || !NetworkServer.active || commandScope == null || (Object)(object)commandScope.Gun != (Object)(object)gun || commandScope.Connection == null || !Finite(direction) || !Finite(point)) { return false; } if (!ActorOwns(commandScope.Connection, gun, out var actor) || !guns.TryGetValue(((NetworkBehaviour)gun).netId, out var value) || !object.ReferenceEquals(value.Gun, gun)) { return false; } if (route == 1) { NetworkBehaviour val = (NetworkBehaviour)((target is NetworkBehaviour) ? target : null); if ((Object)(object)val == (Object)null || val.netId == 0 || !NetworkServer.spawned.TryGetValue(val.netId, out var value2) || !object.ReferenceEquals(value2, val.netIdentity) || ((Vector3)(ref direction)).sqrMagnitude < 0.9f || ((Vector3)(ref direction)).sqrMagnitude > 1.1f) { return false; } } return commandScope.Allowed = value.Magazine.TakeRoute(actor, Now, route); } public static bool NativeShotAllowed(QuotaGun gun) { if (current != null) { if (command != null && (Object)(object)command.Gun == (Object)(object)gun && command.Allowed) { return current.Enabled; } return false; } return true; } private static bool Finite(Vector3 value) { if (GunMagazine.Finite(value.x) && GunMagazine.Finite(value.y)) { return GunMagazine.Finite(value.z); } return false; } public void LateTick() { //IL_00d1: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) RestorePose(); if (Enabled && !((Object)(object)localGun == (Object)null) && !((Object)(object)Held() != (Object)(object)localGun) && !(reloadUntil <= Now) && !reducedMotion()) { Transform transform = ((Component)localGun).transform; if (!((Object)(object)transform.parent == (Object)null) && !((Object)(object)((Item)localGun).NetworkHolder == (Object)null) && !((Object)(object)transform.parent != (Object)(object)((Item)localGun).NetworkHolder.handTransform)) { float num = Mathf.Clamp01((float)((Now - reloadBegan) / 1.5)); float num2 = (float)GunMagazine.Motion(num, reduced: false); pose = transform; poseParent = transform.parent; posePosition = transform.localPosition; poseRotation = transform.localRotation; transform.localPosition = posePosition + new Vector3(-0.035f, -0.085f, -0.045f) * num2; transform.localRotation = poseRotation * Quaternion.Euler(18f * num2, -12f * num2, 22f * num2); } } } private void RestorePose() { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)pose != (Object)null && (Object)(object)pose.parent == (Object)(object)poseParent) { pose.localPosition = posePosition; pose.localRotation = poseRotation; } pose = null; poseParent = null; } public void DrawHud() { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_01bb: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d6: Unknown result type (might be due to invalid IL or missing references) //IL_01e8: Unknown result type (might be due to invalid IL or missing references) //IL_020b: Unknown result type (might be due to invalid IL or missing references) //IL_0222: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Expected O, but got Unknown //IL_0097: 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_00b2: Expected O, but got Unknown //IL_00e1: Unknown result type (might be due to invalid IL or missing references) //IL_043d: Unknown result type (might be due to invalid IL or missing references) //IL_0350: Unknown result type (might be due to invalid IL or missing references) //IL_0355: Unknown result type (might be due to invalid IL or missing references) //IL_035c: Unknown result type (might be due to invalid IL or missing references) //IL_033a: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_04ff: Unknown result type (might be due to invalid IL or missing references) //IL_04b5: Unknown result type (might be due to invalid IL or missing references) //IL_04f0: Unknown result type (might be due to invalid IL or missing references) //IL_03e3: Unknown result type (might be due to invalid IL or missing references) if (!Enabled || (Object)(object)localGun == (Object)null || (Object)(object)Held() != (Object)(object)localGun || overlayOpen() || (int)InputEvents.ActiveLayer != 0 || InputEvents.IsEmoteWheelPressed) { return; } if (titleStyle == null) { GUIStyle val = new GUIStyle(GUI.skin.label); val.fontSize = 13; val.fontStyle = (FontStyle)1; val.alignment = (TextAnchor)3; titleStyle = val; titleStyle.normal.textColor = new Color(0.65f, 0.78f, 0.91f); GUIStyle val2 = new GUIStyle(GUI.skin.label); val2.fontSize = 13; val2.alignment = (TextAnchor)3; hintStyle = val2; hintStyle.normal.textColor = new Color(0.82f, 0.86f, 0.9f); } float num = 218f; float num2 = Mathf.Min((float)GunMagazine.HudScale(Screen.height), Mathf.Max(1f, (float)Screen.width - 24f) / num); num2 = Mathf.Min(num2, Mathf.Max(1f, (float)Screen.height - 24f) / 104f); float num3 = num * num2; float num4 = 104f * num2; GUIStyle obj = titleStyle; int fontSize = (hintStyle.fontSize = GunMagazine.SmallFont(num2)); obj.fontSize = fontSize; float num6 = Mathf.Clamp((float)Screen.width * 0.77f, 8f, (float)Screen.width - num3 - 12f); float num7 = Mathf.Clamp((float)Screen.height * 0.79f, 8f, (float)Screen.height - num4 - 12f); Color color = GUI.color; GUI.color = new Color(0.035f, 0.055f, 0.085f, 0.92f); GUI.DrawTexture(new Rect(num6, num7, num3, num4), (Texture)(object)Texture2D.whiteTexture); GUI.color = new Color(0.26f, 0.68f, 0.93f, 0.95f); GUI.DrawTexture(new Rect(num6, num7, 3f * num2, num4), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; GUI.Label(new Rect(num6 + 14f * num2, num7 + 5f * num2, num3 - 24f * num2, 21f * num2), synced ? ("AMMO " + ammo + " / " + 10) : "AMMO Syncing", titleStyle); for (int i = 0; i < 10; i++) { float num8 = num6 + (float)(17 + i * 18) * num2; float num9 = num7 + 33f * num2; Color colour = ((!synced) ? new Color(0.24f, 0.29f, 0.35f) : ((i < ammo) ? new Color(1f, 0.83f, 0.39f) : ((i < display.AuthoritativeAmmo) ? new Color(0.53f, 0.49f, 0.35f) : new Color(0.2f, 0.27f, 0.34f)))); DrawCartridge(num8, num9, num2, colour); double num10 = display.SpendProgress(i, Now); if (num10 >= 0.0) { float num11 = (float)num10; DrawCartridge(num8 + (reducedMotion() ? 0f : (5f * num11 * num2)), num9 + (reducedMotion() ? 0f : (14f * num11 * num2)), num2, new Color(1f, 0.83f, 0.39f, 1f - num11)); } } double num12 = Math.Max(0.0, reloadUntil - Now); GUI.Label(new Rect(num6 + 14f * num2, num7 + 76f * num2, num3 - 24f * num2, 22f * num2), (pendingReload > 0 && num12 <= 0.0) ? "Reloading" : ((num12 > 0.0) ? ("Reloading " + num12.ToString("0.0") + "s") : "F Reload"), hintStyle); if (num12 > 0.0) { GUI.color = new Color(0.25f, 0.72f, 0.98f); GUI.DrawTexture(new Rect(num6, num7 + num4 - 3f * num2, num3 * Mathf.Clamp01(1f - (float)(num12 / 1.5)), 3f * num2), (Texture)(object)Texture2D.whiteTexture); } GUI.color = color; } private static void DrawCartridge(float x, float y, float scale, Color colour) { //IL_0000: 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_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0074: Unknown result type (might be due to invalid IL or missing references) //IL_009d: 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) GUI.color = colour; GUI.DrawTexture(new Rect(x + 3f * scale, y, 3f * scale, 2f * scale), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(x + 1.5f * scale, y + 2f * scale, 6f * scale, 4f * scale), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(x, y + 6f * scale, 9f * scale, 18f * scale), (Texture)(object)Texture2D.whiteTexture); GUI.DrawTexture(new Rect(x - scale, y + 24f * scale, 11f * scale, 3f * scale), (Texture)(object)Texture2D.whiteTexture); GUI.color = Color.white; } private void Fail(Exception error) { faulted = true; RestorePose(); foreach (HostGun value in guns.Values) { value.Magazine.CancelReload(); value.Magazine.InvalidateShot(); } if (log != null) { log("Gun reload held: " + error.GetBaseException().Message); } } public void Dispose() { if (!disposed) { disposed = true; RestorePose(); guns.Clear(); peers.Clear(); display.Reset(sequence); network.GunRequested -= Request; network.GunStateReceived -= Receive; if (current == this) { current = null; } if (reloadHarmony != null) { reloadHarmony.UnpatchSelf(); } } } internal Action CaptureResetRecovery() { if (!NetworkServer.active || !SelectSave()) { return delegate { }; } Action restore = journal.CaptureRecovery(); return delegate { try { restore(); selectedSave = null; guns.Clear(); display.Reset((sequence == int.MaxValue) ? sequence : (sequence + 1)); reloadUntil = 0.0; pendingReload = 0; } catch (Exception error) { Fail(error); throw; } }; } internal void StartFreshRun() { string text = saveIdentity(); if (!NetworkServer.active || string.IsNullOrEmpty(text)) { throw new InvalidOperationException("Fresh magazines need a native save."); } journal.StartFresh(text); selectedSave = text; foreach (HostGun value in guns.Values) { value.Magazine.Bind(0u); } guns.Clear(); display.Reset((sequence == int.MaxValue) ? sequence : (sequence + 1)); reloadUntil = 0.0; pendingReload = 0; } private void InitializePatches() { Patch(typeof(LocalShootPatch), "Shoot", unused: false); Patch(typeof(LocalUsePatch), "OnUseItem", unused: false); foreach (MethodBase item in NativeInvokerPatch.TargetMethods()) { reloadHarmony.Patch(item, Hook(typeof(NativeInvokerPatch), "Prefix"), Hook(typeof(NativeInvokerPatch), "Postfix"), (HarmonyMethod)null, Hook(typeof(NativeInvokerPatch), "Finalizer"), (HarmonyMethod)null); } foreach (MethodBase item2 in NativeDamagePatch.TargetMethods()) { reloadHarmony.Patch(item2, Hook(typeof(NativeDamagePatch), "Prefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } Patch(typeof(NativeShootEffectsPatch), "UserCode_CmdPlayShootEffects__Vector3", unused: false); Patch(typeof(NativeHitEffectsPatch), "UserCode_CmdPlayHitEffects__Boolean__Vector3", unused: false); reloadHarmony.Patch((MethodBase)AccessTools.Method(typeof(Item), "OnDestroy", (Type[])null, (Type[])null), Hook(typeof(GunDestroyPatch), "Prefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); reloadHarmony.Patch((MethodBase)AccessTools.Method(typeof(Item), "OnStartServer", (Type[])null, (Type[])null), (HarmonyMethod)null, Hook(typeof(GunSpawnPatch), "Postfix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static HarmonyMethod Hook(Type type, string name) { //IL_0009: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Expected O, but got Unknown HarmonyMethod val = new HarmonyMethod(AccessTools.Method(type, name, (Type[])null, (Type[])null)); val.priority = 800; return val; } private void Patch(Type patch, string name, bool unused) { reloadHarmony.Patch((MethodBase)AccessTools.Method(typeof(QuotaGun), name, (Type[])null, (Type[])null), Hook(patch, "Prefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } internal sealed class ChaosNetwork : IDisposable { internal struct GunRequest : NetworkMessage { public string HostNonce; public uint Gun; public int Sequence; public byte Action; } internal struct GunState : NetworkMessage { public string HostNonce; public uint Gun; public uint Owner; public int Sequence; public int AcceptedShot; public byte Ammo; public float Reload; } private sealed class Peer { public readonly NetworkConnectionToClient Connection; public string ClientNonce = ""; public bool Accepted; public bool Rejected; public double NextHello; public int LastActionSequence; public double ActionTokens = 2.0; public double LastActionRefill; public bool NeedsSnapshot; public double NextSnapshot; public Peer(NetworkConnectionToClient connection) { Connection = connection; } } private struct HelloMessage : NetworkMessage { public int Protocol; public string Catalog; public string Version; public string ClientNonce; } private struct AckMessage : NetworkMessage { public bool Accepted; public int Protocol; public string Catalog; public string Version; public string ClientNonce; public string HostNonce; public string Reason; } private struct SnapshotMessage : NetworkMessage { public string HostNonce; public bool RoomReady; public string Payload; } private struct ActionMessage : NetworkMessage { public string HostNonce; public int Sequence; public int Action; public int Index; public int VoteId; public string VoteEpoch; } private struct CompletionMessage : NetworkMessage { public CompletionNotice Notice; } private sealed class OwnedSerializer : IDisposable where T : struct, NetworkMessage { private readonly Action _oldWriter; private readonly Func _oldReader; private readonly Action _writer; private readonly Func _reader; public OwnedSerializer(Action writer, Func reader) { _oldWriter = Writer.write; _oldReader = Reader.read; _writer = writer; _reader = reader; Writer.write = writer; Reader.read = reader; } public void Dispose() { if (object.ReferenceEquals(Writer.write, _writer)) { Writer.write = _oldWriter; } if (object.ReferenceEquals(Reader.read, _reader)) { Reader.read = _oldReader; } } } private sealed class OwnedHandler : IDisposable where T : struct, NetworkMessage { private static readonly FieldInfo ServerTableField = typeof(NetworkServer).GetField("handlers", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static readonly FieldInfo ClientTableField = typeof(NetworkClient).GetField("handlers", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private readonly Action _serverHandler; private readonly Action _clientHandler; private readonly ushort _id; private NetworkMessageDelegate _owned; internal bool HasServerHandler => _serverHandler != null; public OwnedHandler(Action serverHandler, Action clientHandler) { _serverHandler = serverHandler; _clientHandler = clientHandler; _id = NetworkMessages.GetId(); } public bool Ensure() { try { Dictionary table = GetTable(); if (table == null) { return false; } if (table.TryGetValue(_id, out var value)) { return object.ReferenceEquals(value, _owned); } if (NetworkMessages.Lookup.TryGetValue(_id, out var value2) && value2 != typeof(T)) { return false; } if (_serverHandler != null) { NetworkServer.RegisterHandler(_serverHandler, true); } else { NetworkClient.RegisterHandler(_clientHandler, true); } if (!table.TryGetValue(_id, out _owned)) { return false; } return true; } catch { return false; } } public void Dispose() { Dictionary table = GetTable(); RemoveIfOwned(table, _id, _owned, delegate { if (_serverHandler != null) { NetworkServer.UnregisterHandler(); } else { NetworkClient.UnregisterHandler(); } }); } internal static void RemoveIfOwned(Dictionary table, ushort id, NetworkMessageDelegate owned, Action unregister) { if (table != null && owned != null && table.TryGetValue(id, out var value) && object.ReferenceEquals(value, owned)) { unregister(); } } private Dictionary GetTable() { FieldInfo fieldInfo = ((_serverHandler != null) ? ServerTableField : ClientTableField); if (!(fieldInfo == null)) { return fieldInfo.GetValue(null) as Dictionary; } return null; } } internal struct BailContinue : NetworkMessage { public string HostNonce; public long RequestId; public long Revision; } internal struct StatsQueryMessage : NetworkMessage { public string HostNonce; public int RequestId; public ulong Player; } internal struct StatsReplyMessage : NetworkMessage { public string HostNonce; public string Payload; } internal struct StatsPaidMessage : NetworkMessage { public string HostNonce; public string Payload; } private sealed class StatsQueryRate { public NetworkConnectionToClient Connection; public double Next; public bool Pending; public StatsQueryMessage Latest; } private sealed class StatsQueryDelivery { public NetworkConnectionToClient Connection; public StatsQueryMessage Message; } public const int Protocol = 7; public const string Catalog = "chaosnight-0.1.0-r1"; public const string Version = "0.6.4"; public const int MaxSnapshotBytes = 16384; public const int MaxCompletionTaskBytes = 128; public const int MaxCompletionActorBytes = 96; public const int MaxCompletionAmountBytes = 64; public const int MaxCompletionBytes = 512; private const int MaxPeers = 128; private const int MaxActionIndex = 63; internal const int MaxStatsReplyBytes = 32768; internal const int MaxStatsPaidBytes = 1024; private OwnedHandler _gunRequestHandler; private OwnedHandler _gunStateHandler; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private readonly Action _receiveSnapshot; private readonly Func _makeSnapshot; private readonly Action _log; private readonly Stopwatch _clock = new Stopwatch(); private readonly Dictionary _peers = new Dictionary(); private readonly HashSet _seenIds = new HashSet(); private readonly List _removeIds = new List(); private readonly List _serializers = new List(); private readonly Dictionary _lastLogs = new Dictionary(); private string _pendingLog; private double _nextLog; private OwnedHandler _helloHandler; private OwnedHandler _actionHandler; private OwnedHandler _ackHandler; private OwnedHandler _snapshotHandler; private OwnedHandler _completionHandler; private bool _initialized; private bool _disposed; private bool _serverHandlersOkay; private bool _clientHandlersOkay; private bool _wasServerActive; private double _nextTick; private string _hostNonce = ""; private NetworkConnectionToClient _hostLocalConnection; private string _hostFault; private bool _needsBroadcast; private bool _hasCachedSnapshot; private SnapshotMessage _cachedSnapshot; private int _remoteReadyCount; private int _compatibleCount; private int _incompatibleCount; private bool _peerLimitExceeded; private NetworkConnectionToServer _clientConnection; private string _clientNonce = ""; private string _acceptedHostNonce = ""; private bool _clientAccepted; private bool _clientHasSnapshot; private bool _serverRoomReady; private string _clientRejection; private string _clientFault; private double _nextHello; private double _nextGuestAction; private int _clientActionSequence; private long _hostNoticeSequence; private long _clientNoticeSequence; private bool _publishingCompletion; private OwnedHandler _bailContinueHandler; private OwnedHandler _statsQueryHandler; private OwnedHandler _statsReplyHandler; private OwnedHandler _statsPaidHandler; private readonly Dictionary _statsQueryRates = new Dictionary(); public bool ReadyForChaos { get { if (!_initialized || _disposed) { return false; } if (NetworkServer.active) { if (!_wasServerActive || !_serverHandlersOkay || _hostFault != null || !object.ReferenceEquals(_hostLocalConnection, NetworkServer.localConnection)) { return false; } int num = 0; foreach (KeyValuePair connection in NetworkServer.connections) { NetworkConnectionToClient value = connection.Value; if (value != null && !IsLocal(value) && ((NetworkConnection)value).isAuthenticated && ((NetworkConnection)value).isReady) { if (++num > 128) { return false; } if (!_peers.TryGetValue(connection.Key, out var value2) || !value2.Accepted || !object.ReferenceEquals(value2.Connection, value)) { return false; } } } return true; } if (NetworkClient.isConnected && NetworkClient.ready && object.ReferenceEquals(_clientConnection, NetworkClient.connection) && _clientConnection != null && ((NetworkConnection)_clientConnection).isAuthenticated && _clientHandlersOkay) { return HasReadyGuestSnapshot; } return false; } } public bool WaitingForPeerHandshake { get { if (!_initialized || _disposed || !NetworkServer.active || !_wasServerActive || !_serverHandlersOkay || _hostFault != null || !object.ReferenceEquals(_hostLocalConnection, NetworkServer.localConnection)) { return false; } int num = 0; bool pending = false; foreach (KeyValuePair connection in NetworkServer.connections) { NetworkConnectionToClient value = connection.Value; if (value == null || IsLocal(value) || !((NetworkConnection)value).isAuthenticated || !((NetworkConnection)value).isReady) { continue; } if (++num > 128) { return false; } if (!_peers.TryGetValue(connection.Key, out var value2) || !object.ReferenceEquals(value2.Connection, value)) { pending = true; } else if (!value2.Accepted) { if (value2.Rejected) { return false; } pending = true; } } return CanWaitForPeerHandshake(healthyHost: true, num, pending, rejected: false); } } private bool HasReadyGuestSnapshot { get { if (_clientAccepted && _clientHasSnapshot && _serverRoomReady) { return _clientFault == null; } return false; } } public string Status { get { if (_disposed) { return "Chaos Night networking stopped"; } if (!_initialized) { return "Chaos Night networking initializing"; } if (NetworkServer.active) { if (!_wasServerActive) { return "Checking the Chaos Night lobby"; } if (!_serverHandlersOkay) { return "Chaos Night network handler conflict"; } if (_hostFault != null) { return _hostFault; } if (_peerLimitExceeded) { return "Too many clients for Chaos Night networking"; } if (_incompatibleCount > 0) { return "Chaos Night disabled: incompatible mod versions or content"; } if (_compatibleCount != _remoteReadyCount) { return "Waiting for compatible Chaos Night on every player (" + _compatibleCount + "/" + _remoteReadyCount + " guests ready)"; } if (!ReadyForChaos) { return "Waiting for compatible Chaos Night on every player"; } return "Chaos Night ready (" + _compatibleCount + " compatible guests)"; } if (!NetworkClient.isConnected) { return "Waiting for a lobby"; } if (!_clientHandlersOkay) { return "Chaos Night network handler conflict"; } if (_clientRejection != null) { return "Chaos Night disabled: " + _clientRejection; } if (_clientFault != null) { return _clientFault; } if (!_clientAccepted) { return "Checking host Chaos Night compatibility"; } if (!_clientHasSnapshot) { return "Waiting for host Chaos Night state"; } if (!_serverRoomReady) { return "Chaos Night disabled: waiting for compatible players"; } return "Chaos Night synchronized with host"; } } private double Now => _clock.Elapsed.TotalSeconds; public string StatsStream { get { if (!NetworkServer.active) { return _acceptedHostNonce; } return _hostNonce; } } public event Action GunRequested; public event Action GunStateReceived; public event Action HostAction; public event Action HostVote; public event Action CompletionReceived; internal event Action BailContinueRequested; public event Action StatsQueryReceived; public event Action StatsViewReceived; public event Action StatsPaidReceived; private void InitializeGunChannel() { _serializers.Add(new OwnedSerializer(WriteGunRequest, ReadGunRequest)); _serializers.Add(new OwnedSerializer(WriteGunState, ReadGunState)); _gunRequestHandler = new OwnedHandler(OnGunRequest, null); _gunStateHandler = new OwnedHandler(null, OnGunState); } private void DisposeGunChannel() { if (_gunRequestHandler != null) { _gunRequestHandler.Dispose(); } if (_gunStateHandler != null) { _gunStateHandler.Dispose(); } this.GunRequested = null; this.GunStateReceived = null; } internal bool GunPeerAllowed(NetworkConnectionToClient connection) { if (!_disposed && NetworkServer.active && ReadyForChaos && connection != null && ((NetworkConnection)connection).isAuthenticated && ((NetworkConnection)connection).isReady && IsLiveConnection(connection)) { if (!IsLocal(connection)) { return AcceptedStatsPeer(connection); } return true; } return false; } internal bool RequestGun(uint gun, int sequence, byte action) { if (!ReadyForChaos || gun == 0 || sequence <= 0 || action > 2) { return false; } GunRequest gunRequest = new GunRequest { HostNonce = (NetworkServer.active ? _hostNonce : _acceptedHostNonce), Gun = gun, Sequence = sequence, Action = action }; try { NetworkClient.Send(gunRequest, 0); return true; } catch (Exception ex) { Log("gun-send", "Gun request held: " + ex.GetType().Name); return false; } } private void OnGunRequest(NetworkConnectionToClient connection, GunRequest request) { if (GunPeerAllowed(connection) && !(request.HostNonce != _hostNonce) && ValidGunRequest(request)) { this.GunRequested?.Invoke(connection, request); } } internal void ReplyGun(NetworkConnectionToClient connection, GunState state) { if (!GunPeerAllowed(connection)) { return; } state.HostNonce = _hostNonce; if (!ValidGunState(state)) { return; } if (IsLocal(connection)) { this.GunStateReceived?.Invoke(state); return; } try { ((NetworkConnection)connection).Send(state, 0); } catch (Exception ex) { Log("gun-reply", "Gun state delivery delayed: " + ex.GetType().Name); } } private void OnGunState(GunState state) { if (AcceptedStatsHost(state.HostNonce) && ValidGunState(state)) { this.GunStateReceived?.Invoke(state); } } private static bool ValidGunRequest(GunRequest value) { if (ValidNonce(value.HostNonce) && value.Gun != 0 && value.Sequence > 0) { return value.Action <= 2; } return false; } private static bool ValidGunState(GunState value) { if (ValidNonce(value.HostNonce) && value.Gun != 0 && value.Owner != 0 && value.Sequence > 0 && value.Ammo <= 10 && (value.AcceptedShot == 0 || value.AcceptedShot == value.Sequence) && !float.IsNaN(value.Reload) && !float.IsInfinity(value.Reload) && value.Reload >= 0f) { return (double)value.Reload <= 1.5; } return false; } private static void WriteGunRequest(NetworkWriter writer, GunRequest value) { if (!ValidGunRequest(value)) { throw new InvalidDataException("Invalid gun request."); } WriteText(writer, value.HostNonce, 32); NetworkWriterExtensions.WriteUInt(writer, value.Gun); NetworkWriterExtensions.WriteInt(writer, value.Sequence); writer.WriteByte(value.Action); } private static GunRequest ReadGunRequest(NetworkReader reader) { GunRequest gunRequest = new GunRequest { HostNonce = ReadText(reader, 32), Gun = NetworkReaderExtensions.ReadUInt(reader), Sequence = NetworkReaderExtensions.ReadInt(reader), Action = reader.ReadByte() }; if (!ValidGunRequest(gunRequest)) { throw new InvalidDataException("Invalid gun request."); } return gunRequest; } private static void WriteGunState(NetworkWriter writer, GunState value) { if (!ValidGunState(value)) { throw new InvalidDataException("Invalid gun state."); } WriteText(writer, value.HostNonce, 32); NetworkWriterExtensions.WriteUInt(writer, value.Gun); NetworkWriterExtensions.WriteUInt(writer, value.Owner); NetworkWriterExtensions.WriteInt(writer, value.Sequence); NetworkWriterExtensions.WriteInt(writer, value.AcceptedShot); writer.WriteByte(value.Ammo); NetworkWriterExtensions.WriteFloat(writer, value.Reload); } private static GunState ReadGunState(NetworkReader reader) { GunState gunState = new GunState { HostNonce = ReadText(reader, 32), Gun = NetworkReaderExtensions.ReadUInt(reader), Owner = NetworkReaderExtensions.ReadUInt(reader), Sequence = NetworkReaderExtensions.ReadInt(reader), AcceptedShot = NetworkReaderExtensions.ReadInt(reader), Ammo = reader.ReadByte(), Reload = NetworkReaderExtensions.ReadFloat(reader) }; if (!ValidGunState(gunState)) { throw new InvalidDataException("Invalid gun state."); } return gunState; } public ChaosNetwork(Action receiveSnapshot, Func makeSnapshot, Action log) { if (receiveSnapshot == null) { throw new ArgumentNullException("receiveSnapshot"); } if (makeSnapshot == null) { throw new ArgumentNullException("makeSnapshot"); } _receiveSnapshot = receiveSnapshot; _makeSnapshot = makeSnapshot; _log = log; } internal static bool CanWaitForPeerHandshake(bool healthyHost, int remoteCount, bool pending, bool rejected) { if (healthyHost && remoteCount > 0 && remoteCount <= 128 && pending) { return !rejected; } return false; } public void Initialize() { if (_disposed) { throw new ObjectDisposedException("ChaosNetwork"); } if (!_initialized) { _serializers.Add(new OwnedSerializer(WriteHello, ReadHello)); _serializers.Add(new OwnedSerializer(WriteAck, ReadAck)); _serializers.Add(new OwnedSerializer(WriteSnapshot, ReadSnapshot)); _serializers.Add(new OwnedSerializer(WriteAction, ReadAction)); _serializers.Add(new OwnedSerializer(WriteCompletion, ReadCompletion)); _helloHandler = new OwnedHandler(OnHello, null); _actionHandler = new OwnedHandler(OnAction, null); _ackHandler = new OwnedHandler(null, OnAck); _snapshotHandler = new OwnedHandler(null, OnSnapshot); _completionHandler = new OwnedHandler(null, OnCompletion); InitializeStatsChannel(); InitializeGunChannel(); InitializeBailChannel(); _initialized = true; _clock.Start(); EnsureHandlers(); FlushLog(); } } public void Tick() { if (!_initialized || _disposed) { return; } double now = Now; if (now < _nextTick) { return; } _nextTick = now + 1.0; EnsureHandlers(); if (NetworkServer.active) { EnsureHostSession(); ResetClientSession(null); RefreshHostPeers(); FlushStatsQueries(); if (_needsBroadcast) { BroadcastCore(); } else { ResendRequestedSnapshots(); } FlushLog(); } else { if (_wasServerActive) { ResetHostSession(); } PollGuest(now); FlushLog(); } } public void Broadcast() { if (_initialized && !_disposed && NetworkServer.active) { EnsureHostSession(); RefreshHostPeers(); BroadcastCore(); } } public void BroadcastCompletion(CompletionNotice notice) { if (!CanPublishCompletion(_initialized, _disposed, NetworkServer.active, _serverHandlersOkay, _publishingCompletion)) { return; } if (!ValidCompletionContent(notice)) { Log("completion-bound", "Chaos Night completion text is empty, invalid or exceeds its UTF-8 bounds."); return; } EnsureHostSession(); if (_hostNoticeSequence == long.MaxValue) { Log("completion-sequence", "Chaos Night completion sequence is exhausted."); return; } RefreshHostPeers(); CompletionNotice completionNotice = CopyCompletion(notice); completionNotice.Session = _hostNonce; completionNotice.Sequence = ++_hostNoticeSequence; CompletionMessage completionMessage = new CompletionMessage { Notice = completionNotice }; _publishingCompletion = true; try { DeliverCompletion(completionNotice); foreach (KeyValuePair peer in _peers) { Peer value = peer.Value; NetworkConnectionToClient connection = value.Connection; if (value.Accepted && connection != null && !IsLocal(connection) && ((NetworkConnection)connection).isAuthenticated && ((NetworkConnection)connection).isReady && IsLiveConnection(connection)) { try { ((NetworkConnection)connection).Send(completionMessage, 0); } catch (Exception ex) { Log("completion-send", "Chaos Night completion send failed: " + ex.GetType().Name); } } } } finally { _publishingCompletion = false; } } private static bool CanPublishCompletion(bool initialized, bool disposed, bool hostActive, bool handlersOkay, bool publishing) { if (initialized && !disposed && hostActive && handlersOkay) { return !publishing; } return false; } public void GuestSendAction(int action, int index) { if (!ReadyForChaos || NetworkServer.active || !ValidAction(action, index)) { return; } double now = Now; if (now < _nextGuestAction || _clientActionSequence == int.MaxValue) { return; } _nextGuestAction = now + 0.5; _clientActionSequence++; ActionMessage actionMessage = new ActionMessage { HostNonce = _acceptedHostNonce, Sequence = _clientActionSequence, Action = action, Index = index }; try { NetworkClient.Send(actionMessage, 0); } catch (Exception ex) { Log("action-send", "Chaos Night request send failed: " + ex.GetType().Name); } } public void GuestSendVote(int voteId, string epoch, bool yes) { if (!ReadyForChaos || NetworkServer.active || voteId <= 0 || !ValidNonce(epoch)) { return; } double now = Now; if (now < _nextGuestAction || _clientActionSequence == int.MaxValue) { return; } _nextGuestAction = now + 0.5; _clientActionSequence++; ActionMessage actionMessage = new ActionMessage { HostNonce = _acceptedHostNonce, Sequence = _clientActionSequence, Action = 6, Index = (yes ? 1 : 0), VoteId = voteId, VoteEpoch = epoch }; try { NetworkClient.Send(actionMessage, 0); } catch (Exception ex) { Log("vote-send", "Chaos Night vote send failed: " + ex.GetType().Name); } } private void EnsureHandlers() { bool flag = _helloHandler.Ensure(); bool flag2 = _actionHandler.Ensure(); bool flag3 = _ackHandler.Ensure(); bool flag4 = _snapshotHandler.Ensure(); bool flag5 = _completionHandler.Ensure(); _serverHandlersOkay = flag && flag2 && _statsQueryHandler.Ensure() && _gunRequestHandler.Ensure() && _bailContinueHandler.Ensure(); _clientHandlersOkay = flag3 && flag4 && flag5 && _statsReplyHandler.Ensure() && _statsPaidHandler.Ensure() && _gunStateHandler.Ensure(); if (!_serverHandlersOkay || !_clientHandlersOkay) { Log("handler-conflict", "Chaos Night found a network handler conflict; Chaos remains disabled."); } } private void EnsureHostSession() { if (!_wasServerActive || !object.ReferenceEquals(_hostLocalConnection, NetworkServer.localConnection)) { _wasServerActive = true; _hostLocalConnection = (NetworkConnectionToClient)(object)NetworkServer.localConnection; _hostNonce = Guid.NewGuid().ToString("N"); _hostNoticeSequence = 0L; _hostFault = null; _peers.Clear(); _statsQueryRates.Clear(); _remoteReadyCount = 0; _compatibleCount = 0; _incompatibleCount = 0; _peerLimitExceeded = false; _hasCachedSnapshot = false; _needsBroadcast = true; } } private void ResetHostSession() { _wasServerActive = false; _hostNonce = ""; _hostNoticeSequence = 0L; _hostLocalConnection = null; _hostFault = null; _peers.Clear(); _seenIds.Clear(); _removeIds.Clear(); _remoteReadyCount = 0; _compatibleCount = 0; _incompatibleCount = 0; _peerLimitExceeded = false; _hasCachedSnapshot = false; _needsBroadcast = false; } private void RefreshHostPeers() { int remoteReadyCount = _remoteReadyCount; int compatibleCount = _compatibleCount; int incompatibleCount = _incompatibleCount; bool peerLimitExceeded = _peerLimitExceeded; _remoteReadyCount = 0; _compatibleCount = 0; _incompatibleCount = 0; _peerLimitExceeded = false; _seenIds.Clear(); foreach (KeyValuePair connection in NetworkServer.connections) { NetworkConnectionToClient value = connection.Value; if (value == null || IsLocal(value)) { continue; } _seenIds.Add(connection.Key); if (!((NetworkConnection)value).isAuthenticated || !((NetworkConnection)value).isReady) { continue; } _remoteReadyCount++; if (!_peers.TryGetValue(connection.Key, out var value2) || !object.ReferenceEquals(value2.Connection, value)) { if (_peers.Count >= 128) { _peerLimitExceeded = true; continue; } value2 = new Peer(value); _peers[connection.Key] = value2; } if (value2.Accepted) { _compatibleCount++; } else if (value2.Rejected) { _incompatibleCount++; } } _removeIds.Clear(); foreach (KeyValuePair peer in _peers) { if (!_seenIds.Contains(peer.Key) || !NetworkServer.connections.TryGetValue(peer.Key, out var value3) || !object.ReferenceEquals(value3, peer.Value.Connection)) { _removeIds.Add(peer.Key); } } for (int i = 0; i < _removeIds.Count; i++) { _peers.Remove(_removeIds[i]); _statsQueryRates.Remove(_removeIds[i]); } if (_remoteReadyCount > 128) { _peerLimitExceeded = true; } if (remoteReadyCount != _remoteReadyCount || compatibleCount != _compatibleCount || incompatibleCount != _incompatibleCount || peerLimitExceeded != _peerLimitExceeded) { _needsBroadcast = true; } } private static bool IsLocal(NetworkConnectionToClient connection) { if (!(connection is LocalConnectionToClient)) { return object.ReferenceEquals(connection, NetworkServer.localConnection); } return true; } private void PollGuest(double now) { NetworkConnectionToServer connection = NetworkClient.connection; if (!NetworkClient.isConnected || connection == null) { ResetClientSession(null); return; } if (!object.ReferenceEquals(_clientConnection, connection)) { ResetClientSession(connection); } if (!NetworkClient.ready || !((NetworkConnection)connection).isAuthenticated || !_clientHandlersOkay || _clientRejection != null || (_clientAccepted && _clientHasSnapshot) || now < _nextHello) { return; } _nextHello = now + 2.0; HelloMessage helloMessage = new HelloMessage { Protocol = 7, Catalog = "chaosnight-0.1.0-r1", Version = "0.6.4", ClientNonce = _clientNonce }; try { NetworkClient.Send(helloMessage, 0); } catch (Exception ex) { Log("hello-send", "Chaos Night compatibility request failed: " + ex.GetType().Name); } } private void ResetClientSession(NetworkConnectionToServer connection) { if (!object.ReferenceEquals(_clientConnection, connection)) { _clientConnection = connection; _clientNonce = ((connection == null) ? "" : Guid.NewGuid().ToString("N")); _acceptedHostNonce = ""; _clientAccepted = false; _clientHasSnapshot = false; _serverRoomReady = false; _clientRejection = null; _clientFault = null; _nextHello = 0.0; _nextGuestAction = 0.0; _clientActionSequence = 0; _clientNoticeSequence = 0L; } } private void OnHello(NetworkConnectionToClient connection, HelloMessage message) { if (_disposed || !NetworkServer.active || !_serverHandlersOkay || connection == null || IsLocal(connection) || !((NetworkConnection)connection).isAuthenticated || !((NetworkConnection)connection).isReady || !IsLiveConnection(connection) || !ValidNonce(message.ClientNonce)) { return; } EnsureHostSession(); if (!_peers.TryGetValue(connection.connectionId, out var value) || !object.ReferenceEquals(value.Connection, connection)) { if (_peers.Count >= 128) { return; } value = new Peer(connection); _peers[connection.connectionId] = value; } double now = Now; if (!(now < value.NextHello)) { value.NextHello = now + 1.0; bool flag = message.Protocol == 7 && message.Catalog == "chaosnight-0.1.0-r1" && message.Version == "0.6.4"; if (value.ClientNonce != message.ClientNonce) { value.LastActionSequence = 0; value.ActionTokens = 2.0; value.LastActionRefill = now; } value.ClientNonce = message.ClientNonce; value.Accepted = flag; value.Rejected = !flag; value.NeedsSnapshot = flag; AckMessage ackMessage = new AckMessage { Accepted = flag, Protocol = 7, Catalog = "chaosnight-0.1.0-r1", Version = "0.6.4", ClientNonce = message.ClientNonce, HostNonce = _hostNonce, Reason = (flag ? "" : "Install the same Chaos Night version and content as the host") }; try { ((NetworkConnection)connection).Send(ackMessage, 0); } catch (Exception ex) { value.Accepted = false; Log("ack-send", "Chaos Night compatibility reply failed: " + ex.GetType().Name); } RefreshHostPeers(); if (!flag) { Log("incompatible", "A guest has incompatible Chaos Night; the mode stays disabled for this lobby."); } } } private bool IsLiveConnection(NetworkConnectionToClient connection) { if (NetworkServer.connections.TryGetValue(connection.connectionId, out var value)) { return object.ReferenceEquals(value, connection); } return false; } private void OnAck(AckMessage message) { if (_disposed || NetworkServer.active || !NetworkClient.isConnected || _clientConnection == null || !object.ReferenceEquals(_clientConnection, NetworkClient.connection) || message.ClientNonce != _clientNonce || !ValidNonce(message.HostNonce)) { return; } if (!message.Accepted || message.Protocol != 7 || !(message.Catalog == "chaosnight-0.1.0-r1") || !(message.Version == "0.6.4")) { _clientAccepted = false; _clientHasSnapshot = false; _serverRoomReady = false; _clientRejection = "Install the same Chaos Night version and content as the host"; return; } if (_acceptedHostNonce != message.HostNonce) { _clientHasSnapshot = false; _serverRoomReady = false; _clientActionSequence = 0; _clientNoticeSequence = 0L; } _acceptedHostNonce = message.HostNonce; _clientAccepted = true; _clientRejection = null; } private void OnSnapshot(SnapshotMessage message) { if (!_disposed && !NetworkServer.active && _clientAccepted && !(message.HostNonce != _acceptedHostNonce) && NetworkClient.isConnected && _clientConnection != null && object.ReferenceEquals(_clientConnection, NetworkClient.connection)) { ReceiveSnapshotPayload(message); } } private void OnCompletion(CompletionMessage message) { ReceiveCompletionPayload(NetworkServer.active, NetworkClient.isConnected, _clientConnection != null && ((NetworkConnection)_clientConnection).isAuthenticated, _clientConnection != null && object.ReferenceEquals(_clientConnection, NetworkClient.connection), message.Notice); } private void ReceiveCompletionPayload(bool hostActive, bool connected, bool authenticated, bool currentConnection, CompletionNotice notice) { if (CanReceiveCompletion(_disposed, hostActive, connected, authenticated, currentConnection, _clientAccepted, _acceptedHostNonce, _clientNoticeSequence, notice)) { _clientNoticeSequence = notice.Sequence; DeliverCompletion(notice); } } private static bool CanReceiveCompletion(bool disposed, bool hostActive, bool connected, bool authenticated, bool currentConnection, bool accepted, string hostSession, long lastSequence, CompletionNotice notice) { if (!disposed && !hostActive && connected && authenticated && currentConnection && accepted && ValidCompletion(notice) && notice.Session == hostSession) { return notice.Sequence > lastSequence; } return false; } private void DeliverCompletion(CompletionNotice notice) { Action completionReceived = this.CompletionReceived; if (completionReceived == null) { return; } Delegate[] invocationList = completionReceived.GetInvocationList(); foreach (Delegate obj in invocationList) { try { ((Action)obj)(CopyCompletion(notice)); } catch (Exception ex) { Log("completion-handler", "Chaos Night completion presentation failed: " + ex.GetType().Name); } } } private static CompletionNotice CopyCompletion(CompletionNotice notice) { CompletionNotice completionNotice = new CompletionNotice(); completionNotice.Session = notice.Session; completionNotice.Sequence = notice.Sequence; completionNotice.Task = notice.Task; completionNotice.Actor = notice.Actor; completionNotice.Amount = notice.Amount; completionNotice.Penalty = notice.Penalty; return completionNotice; } private static bool ValidCompletion(CompletionNotice notice) { if (notice != null && ValidNonce(notice.Session) && notice.Sequence > 0) { return ValidCompletionContent(notice); } return false; } private static bool ValidCompletionContent(CompletionNotice notice) { if (notice != null && ValidCompletionText(notice.Task, 128) && ValidCompletionText(notice.Actor, 96)) { return ValidCompletionText(notice.Amount, 64); } return false; } private static bool ValidCompletionText(string value, int maximum) { if (string.IsNullOrWhiteSpace(value) || value.Length > maximum) { return false; } try { return Utf8.GetByteCount(value) <= maximum; } catch (EncoderFallbackException) { return false; } } private void ReceiveSnapshotPayload(SnapshotMessage message) { try { if (message.Payload == null || Utf8.GetByteCount(message.Payload) > 16384) { throw new InvalidDataException("Invalid snapshot length"); } _serverRoomReady = message.RoomReady; _clientHasSnapshot = true; _clientFault = null; _receiveSnapshot(message.Payload); } catch (Exception ex) { _clientHasSnapshot = false; _serverRoomReady = false; _clientFault = "Chaos Night state could not be validated"; Log("snapshot-read", "Chaos Night rejected host state: " + ex.GetType().Name); } } private void OnAction(NetworkConnectionToClient connection, ActionMessage message) { if (_disposed || !NetworkServer.active || !ReadyForChaos || connection == null || !((NetworkConnection)connection).isAuthenticated || !((NetworkConnection)connection).isReady || IsLocal(connection) || !IsLiveConnection(connection) || message.HostNonce != _hostNonce || message.Sequence <= 0 || !ValidRequest(message.Action, message.Index, message.VoteId, message.VoteEpoch) || !_peers.TryGetValue(connection.connectionId, out var value) || !value.Accepted || !object.ReferenceEquals(value.Connection, connection) || message.Sequence <= value.LastActionSequence) { return; } double now = Now; value.ActionTokens = Math.Min(2.0, value.ActionTokens + Math.Max(0.0, now - value.LastActionRefill) * 2.0); value.LastActionRefill = now; if (value.ActionTokens < 1.0) { return; } value.ActionTokens -= 1.0; value.LastActionSequence = message.Sequence; if (message.Action == 6) { Action hostVote = this.HostVote; if (hostVote == null) { return; } try { hostVote(message.VoteId, message.VoteEpoch, message.Index == 1, connection); return; } catch (Exception ex) { Log("vote-handler", "Chaos Night host rejected a vote: " + ex.GetType().Name); return; } } Action hostAction = this.HostAction; if (hostAction == null) { return; } try { hostAction(message.Action, message.Index, connection); } catch (Exception ex2) { Log("action-handler", "Chaos Night host rejected a request: " + ex2.GetType().Name); } } private static bool ValidAction(int action, int index) { if (action >= 1 && action <= 3 && index >= 0) { return index <= 63; } return false; } private static bool ValidRequest(int action, int index, int voteId, string epoch) { if (action != 6) { if (ValidAction(action, index) && voteId == 0) { return string.IsNullOrEmpty(epoch); } return false; } if ((index == 0 || index == 1) && voteId > 0) { return ValidNonce(epoch); } return false; } private static bool ValidNonce(string nonce) { if (nonce == null || nonce.Length != 32) { return false; } foreach (char c in nonce) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { return false; } } return true; } private void BroadcastCore() { if (!NetworkServer.active || !_serverHandlersOkay) { return; } _needsBroadcast = false; string text; try { text = _makeSnapshot(); if (text == null || Utf8.GetByteCount(text) > 16384) { throw new InvalidDataException("Snapshot exceeds the 16 KiB limit"); } _hostFault = null; } catch (Exception ex) { _hostFault = "Chaos Night disabled: host state exceeds limits or could not be created"; Log("snapshot-build", "Chaos Night could not build its bounded snapshot: " + ex.GetType().Name); return; } SnapshotMessage snapshotMessage = (_cachedSnapshot = new SnapshotMessage { HostNonce = _hostNonce, RoomReady = ReadyForChaos, Payload = text }); _hasCachedSnapshot = true; foreach (KeyValuePair peer in _peers) { Peer value = peer.Value; NetworkConnectionToClient connection = value.Connection; if (value.Accepted && connection != null && !IsLocal(connection) && ((NetworkConnection)connection).isAuthenticated && ((NetworkConnection)connection).isReady && IsLiveConnection(connection)) { try { ((NetworkConnection)connection).Send(snapshotMessage, 0); value.NeedsSnapshot = false; value.NextSnapshot = Now + 2.0; } catch (Exception ex2) { value.Accepted = false; _compatibleCount = Math.Max(0, _compatibleCount - 1); _needsBroadcast = true; Log("snapshot-send", "Chaos Night state send failed: " + ex2.GetType().Name); } } } } private void ResendRequestedSnapshots() { if (!_hasCachedSnapshot || !NetworkServer.active || !_serverHandlersOkay) { return; } double now = Now; foreach (KeyValuePair peer in _peers) { Peer value = peer.Value; NetworkConnectionToClient connection = value.Connection; if (value.Accepted && value.NeedsSnapshot && !(now < value.NextSnapshot) && connection != null && !IsLocal(connection) && ((NetworkConnection)connection).isAuthenticated && ((NetworkConnection)connection).isReady && IsLiveConnection(connection)) { try { ((NetworkConnection)connection).Send(_cachedSnapshot, 0); value.NeedsSnapshot = false; value.NextSnapshot = now + 2.0; } catch (Exception ex) { value.Accepted = false; _compatibleCount = Math.Max(0, _compatibleCount - 1); _needsBroadcast = true; Log("snapshot-send", "Chaos Night state send failed: " + ex.GetType().Name); } } } } private void Log(string key, string message) { if (_log != null && (!_lastLogs.TryGetValue(key, out var value) || !(value == message))) { _lastLogs[key] = message; _pendingLog = message; } } private void FlushLog() { if (_log == null || _pendingLog == null || Now < _nextLog) { return; } string pendingLog = _pendingLog; _pendingLog = null; _nextLog = Now + 1.0; try { _log(pendingLog); } catch { } } public static string SelfTest() { //IL_03f1: Unknown result type (might be due to invalid IL or missing references) //IL_03f8: Expected O, but got Unknown //IL_0413: Unknown result type (might be due to invalid IL or missing references) //IL_0422: Expected O, but got Unknown //IL_03bf: Unknown result type (might be due to invalid IL or missing references) //IL_03dd: Expected O, but got Unknown try { using (new OwnedSerializer(WriteHello, ReadHello)) { using (new OwnedSerializer(WriteAck, ReadAck)) { using (new OwnedSerializer(WriteSnapshot, ReadSnapshot)) { using (new OwnedSerializer(WriteAction, ReadAction)) { using (new OwnedSerializer(WriteCompletion, ReadCompletion)) { string text = "0123456789abcdef0123456789abcdef"; HelloMessage helloMessage = RoundTrip(new HelloMessage { Protocol = 7, Catalog = "chaosnight-0.1.0-r1", Version = "0.6.4", ClientNonce = text }); Assert(helloMessage.Protocol == 7 && helloMessage.Catalog == "chaosnight-0.1.0-r1" && helloMessage.Version == "0.6.4" && helloMessage.ClientNonce == text, "hello roundtrip"); AckMessage ackMessage = RoundTrip(new AckMessage { Accepted = true, Protocol = 7, Catalog = "chaosnight-0.1.0-r1", Version = "0.6.4", ClientNonce = text, HostNonce = text, Reason = "compatible" }); Assert(ackMessage.Accepted && ackMessage.Protocol == 7 && ackMessage.Catalog == "chaosnight-0.1.0-r1" && ackMessage.Version == "0.6.4" && ackMessage.ClientNonce == text && ackMessage.HostNonce == text && ackMessage.Reason == "compatible", "ack roundtrip"); SnapshotMessage message = new SnapshotMessage { HostNonce = text, RoomReady = true, Payload = new string('x', 16380) + "éé" }; SnapshotMessage snapshotMessage = RoundTrip(message); Assert(snapshotMessage.HostNonce == text && snapshotMessage.RoomReady && snapshotMessage.Payload == message.Payload && Utf8.GetByteCount(snapshotMessage.Payload) == 16384, "16 KiB UTF-8 snapshot roundtrip"); ActionMessage message2 = new ActionMessage { HostNonce = text, Sequence = 41, Action = 3, Index = 63 }; ActionMessage actionMessage = RoundTrip(message2); Assert(actionMessage.HostNonce == text && actionMessage.Sequence == 41 && actionMessage.Action == 3 && actionMessage.Index == 63, "action roundtrip"); message2.Action = 6; message2.Index = 1; message2.VoteId = 23; message2.VoteEpoch = text; actionMessage = RoundTrip(message2); Assert(actionMessage.Action == 6 && actionMessage.Index == 1 && actionMessage.VoteId == 23 && actionMessage.VoteEpoch == text, "bounded vote roundtrip"); Assert(ValidRequest(6, 0, 23, text) && ValidRequest(6, 1, 23, text) && !ValidRequest(6, 2, 23, text) && !ValidRequest(6, 1, 0, text) && !ValidRequest(6, 1, 23, "bad") && !ValidRequest(1, 0, 23, text) && !ValidRequest(5, 0, 0, null) && !ValidRequest(7, 0, 0, null), "vote and host-only request bounds"); bool condition = false; try { WriteText(new NetworkWriter(), new string('é', 8193), 16384); } catch (InvalidDataException) { condition = true; } Assert(condition, "oversized UTF-8 write rejection"); NetworkWriter val = new NetworkWriter(); NetworkWriterExtensions.WriteInt(val, 16385); condition = false; try { ReadText(new NetworkReader(new ArraySegment(val.ToArray())), 16384); } catch (InvalidDataException) { condition = true; } Assert(condition, "oversized read rejection before allocation"); Assert(ValidAction(1, 0) && ValidAction(3, 63) && !ValidAction(0, 0) && !ValidAction(4, 0) && !ValidAction(1, -1) && !ValidAction(1, 64), "action bounds"); Assert(ValidNonce(text) && !ValidNonce("") && !ValidNonce(new string('z', 32)), "session nonce bounds"); VerifySnapshotCallbackOrder(); VerifyCompletionNotices(text); } } } } } return "PASS: Mirror serializers and bounds; guest snapshot ready before presentation, fault recovery and rejection rollback; protocol 4 completion serialization, host ownership, session deduplication and callback isolation"; } catch (Exception ex3) { return "FAIL: Chaos Night network self-test: " + ex3.GetType().Name + ": " + ex3.Message; } } private static void VerifySnapshotCallbackOrder() { SnapshotMessage message = new SnapshotMessage { RoomReady = true, Payload = "callback-ready" }; ChaosNetwork probe = null; bool presented = false; probe = new ChaosNetwork(delegate(string payload) { Assert(probe.HasReadyGuestSnapshot, "guest readiness inside first snapshot presentation"); Assert(payload == "callback-ready", "presentation receives the current payload"); presented = true; }, () => "", null); using (probe) { probe._clientAccepted = true; probe.ReceiveSnapshotPayload(message); Assert(presented && probe.HasReadyGuestSnapshot, "first guest snapshot presentation succeeds"); probe._clientHasSnapshot = false; probe._clientFault = "earlier invalid snapshot"; presented = false; probe.ReceiveSnapshotPayload(message); Assert(presented && probe.HasReadyGuestSnapshot, "valid snapshot recovers before presentation"); } using (ChaosNetwork chaosNetwork = new ChaosNetwork(delegate { throw new InvalidDataException("invalid UI envelope"); }, () => "", null)) { chaosNetwork._clientAccepted = true; chaosNetwork.ReceiveSnapshotPayload(message); Assert(!chaosNetwork.HasReadyGuestSnapshot && !chaosNetwork._clientHasSnapshot && !chaosNetwork._serverRoomReady && chaosNetwork._clientFault != null, "callback rejection rolls guest readiness back"); } bool oversizedPresented = false; using ChaosNetwork chaosNetwork2 = new ChaosNetwork(delegate { oversizedPresented = true; throw new InvalidOperationException("oversized snapshot must not reach presentation"); }, () => "", null); chaosNetwork2._clientAccepted = true; chaosNetwork2._clientHasSnapshot = true; chaosNetwork2._serverRoomReady = true; message.Payload = new string('x', 16385); chaosNetwork2.ReceiveSnapshotPayload(message); Assert(!oversizedPresented && !chaosNetwork2.HasReadyGuestSnapshot && !chaosNetwork2._clientHasSnapshot && !chaosNetwork2._serverRoomReady && chaosNetwork2._clientFault != null, "payload rejection rolls guest readiness back"); } private static void VerifyCompletionNotices(string nonce) { //IL_0177: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Expected O, but got Unknown //IL_0605: Unknown result type (might be due to invalid IL or missing references) //IL_060b: Expected O, but got Unknown //IL_06c9: Unknown result type (might be due to invalid IL or missing references) //IL_06d0: Expected O, but got Unknown //IL_06ea: Unknown result type (might be due to invalid IL or missing references) //IL_06f1: Expected O, but got Unknown //IL_0721: Unknown result type (might be due to invalid IL or missing references) //IL_073d: Expected O, but got Unknown HashSet hashSet = new HashSet(); hashSet.Add(NetworkMessages.GetId()); hashSet.Add(NetworkMessages.GetId()); hashSet.Add(NetworkMessages.GetId()); hashSet.Add(NetworkMessages.GetId()); hashSet.Add(NetworkMessages.GetId()); HashSet hashSet2 = hashSet; Assert(hashSet2.Count == 5, "completion message id differs from every existing Chaos message"); CompletionNotice completionNotice = new CompletionNotice(); completionNotice.Session = nonce; completionNotice.Sequence = long.MaxValue; completionNotice.Task = new string('é', 64); completionNotice.Actor = new string('é', 48); completionNotice.Amount = new string('9', 64); completionNotice.Penalty = true; CompletionNotice completionNotice2 = completionNotice; CompletionMessage message = RoundTrip(new CompletionMessage { Notice = completionNotice2 }); Assert(message.Notice.Session == completionNotice2.Session && message.Notice.Sequence == long.MaxValue && message.Notice.Task == completionNotice2.Task && message.Notice.Actor == completionNotice2.Actor && message.Notice.Amount == completionNotice2.Amount && message.Notice.Penalty, "bounded completion roundtrip preserves UTF-8 and 64-bit identity"); NetworkWriter val = new NetworkWriter(); WriteCompletion(val, message); Assert(val.Position <= 512, "maximum completion fits 512-byte wire budget"); CompletionNotice completionNotice3 = new CompletionNotice(); completionNotice3.Session = nonce; completionNotice3.Sequence = 1L; completionNotice3.Task = "Gun ready for pickup"; completionNotice3.Actor = "Buyer"; completionNotice3.Amount = "1 ticket"; CompletionNotice completionNotice4 = completionNotice3; CompletionNotice notice = RoundTrip(new CompletionMessage { Notice = completionNotice4 }).Notice; Assert(notice.IsGunPurchase() && notice.Actor == "Buyer", "purchase receipt uses existing bounded native-ticket notice without cash labeling"); Assert(!completionNotice2.IsGunPurchase(), "cash penalty notice cannot become a purchase receipt"); notice.Penalty = true; Assert(!notice.IsGunPurchase(), "purchase label with penalty flag stays a penalty"); notice.Penalty = false; notice.Amount = "$1"; Assert(!notice.IsGunPurchase(), "cash reward cannot become a ticket purchase"); int purchaseDelivered = 0; using (ChaosNetwork chaosNetwork = new ChaosNetwork(delegate { }, () => "", null)) { chaosNetwork._clientAccepted = true; chaosNetwork._acceptedHostNonce = nonce; chaosNetwork.CompletionReceived += delegate(CompletionNotice receipt) { Assert(receipt.IsGunPurchase(), "guest receipt preserves purchase presentation"); purchaseDelivered++; }; chaosNetwork.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice4); chaosNetwork.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice4); Assert(purchaseDelivered == 1, "accepted purchase receipt displayed once to authenticated guest"); completionNotice4.Sequence = 2L; chaosNetwork.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: false, currentConnection: true, completionNotice4); chaosNetwork.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: false, completionNotice4); Assert(purchaseDelivered == 1, "unauthenticated and replaced connections cannot deliver purchase receipts"); } completionNotice2.Task = "Crew Relay"; completionNotice2.Actor = "Player"; completionNotice2.Amount = "$12.00"; completionNotice2.Sequence = 1L; completionNotice2.Penalty = false; Assert(!RoundTrip(new CompletionMessage { Notice = completionNotice2 }).Notice.Penalty, "false penalty flag roundtrip preserves zero"); Assert(ValidCompletion(completionNotice2), "valid completion accepted"); completionNotice2.Sequence = 0L; Assert(!ValidCompletion(completionNotice2), "zero completion id rejected"); completionNotice2.Sequence = -1L; Assert(!ValidCompletion(completionNotice2), "negative completion id rejected"); completionNotice2.Sequence = 1L; completionNotice2.Session = "unknown"; Assert(!ValidCompletion(completionNotice2), "invalid completion epoch rejected"); completionNotice2.Session = nonce; completionNotice2.Task = null; Assert(!ValidCompletion(completionNotice2), "null completion task rejected"); completionNotice2.Task = "Crew Relay"; completionNotice2.Actor = null; Assert(!ValidCompletion(completionNotice2), "null completion actor rejected"); completionNotice2.Actor = "Player"; completionNotice2.Amount = null; Assert(!ValidCompletion(completionNotice2), "null completion amount rejected"); completionNotice2.Amount = "$12.00"; completionNotice2.Task = " "; Assert(!ValidCompletion(completionNotice2), "empty completion label rejected"); completionNotice2.Task = new string('é', 65); Assert(!ValidCompletion(completionNotice2), "task UTF-8 overrun rejected"); completionNotice2.Task = "Crew Relay"; completionNotice2.Actor = new string('é', 49); Assert(!ValidCompletion(completionNotice2), "actor UTF-8 overrun rejected"); completionNotice2.Actor = "Player"; completionNotice2.Amount = new string('9', 65); Assert(!ValidCompletion(completionNotice2), "amount overrun rejected"); completionNotice2.Amount = "$12.00"; completionNotice2.Actor = "\ud800"; Assert(!ValidCompletion(completionNotice2), "malformed surrogate rejected"); completionNotice2.Actor = "Player"; Assert(!ValidCompletion(null), "null notice rejected"); Action write = Writer.write; Func read = Reader.read; using (new OwnedSerializer(WriteCompletion, ReadCompletion)) { RoundTrip(new CompletionMessage { Notice = completionNotice2 }); } Assert(object.ReferenceEquals(Writer.write, write) && object.ReferenceEquals(Reader.read, read), "notice serializer ownership restored exactly"); val = new NetworkWriter(); WriteCompletion(val, new CompletionMessage { Notice = completionNotice2 }); byte[] array = val.ToArray(); array[^1] = 2; ExpectBadCompletionPacket(array, "unknown completion penalty flag rejected"); array = val.ToArray(); Array.Clear(array, 36, 8); ExpectBadCompletionPacket(array, "zero sequence wire packet rejected"); array = val.ToArray(); for (int num = 36; num < 44; num++) { array[num] = byte.MaxValue; } ExpectBadCompletionPacket(array, "negative sequence wire packet rejected"); ExpectBadCompletionPacket(new byte[4] { 255, 255, 255, 255 }, "unknown packet rejected before allocation"); ExpectBadCompletionPacket(new byte[5] { 32, 0, 0, 0, 1 }, "truncated completion packet rejected"); NetworkWriter val2 = new NetworkWriter(); NetworkWriterExtensions.WriteInt(val2, 33); ExpectBadCompletionPacket(val2.ToArray(), "oversized epoch rejected before allocation"); NetworkWriter val3 = new NetworkWriter(); NetworkWriterExtensions.WriteInt(val3, 1); val3.WriteByte(byte.MaxValue); ExpectBadCompletionPacket(val3.ToArray(), "malformed UTF-8 packet rejected"); completionNotice2.Sequence = 0L; bool condition = false; try { WriteCompletion(new NetworkWriter(), new CompletionMessage { Notice = completionNotice2 }); } catch (InvalidDataException) { condition = true; } Assert(condition, "invalid completion cannot be serialized"); completionNotice2.Sequence = 1L; Assert(CanPublishCompletion(initialized: true, disposed: false, hostActive: true, handlersOkay: true, publishing: false) && !CanPublishCompletion(initialized: true, disposed: false, hostActive: false, handlersOkay: true, publishing: false) && !CanPublishCompletion(initialized: false, disposed: false, hostActive: true, handlersOkay: true, publishing: false) && !CanPublishCompletion(initialized: true, disposed: true, hostActive: true, handlersOkay: true, publishing: false) && !CanPublishCompletion(initialized: true, disposed: false, hostActive: true, handlersOkay: false, publishing: false) && !CanPublishCompletion(initialized: true, disposed: false, hostActive: true, handlersOkay: true, publishing: true), "only initialized authoritative host may publish; recursive publications rejected"); int delivered = 0; ChaosNetwork probe = new ChaosNetwork(delegate { }, () => "", null); try { probe._clientAccepted = true; probe._acceptedHostNonce = nonce; probe.CompletionReceived += delegate(CompletionNotice completionNotice5) { Assert(probe._clientNoticeSequence == completionNotice5.Sequence, "notice sequence committed before presentation"); completionNotice5.Task = "listener mutation"; throw new InvalidOperationException("intentional presentation failure"); }; probe.CompletionReceived += delegate(CompletionNotice completionNotice5) { Assert(completionNotice5.Task == "Crew Relay", "listeners receive separate immutable-by-ownership notice copies"); delivered++; }; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 1 && completionNotice2.Task == "Crew Relay", "callback exception isolated; shared outgoing data unchanged"); probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 1, "duplicate notice delivered only once"); completionNotice2.Sequence = 0L; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); completionNotice2.Sequence = 2L; completionNotice2.Session = "abcdef0123456789abcdef0123456789"; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 1, "old or unknown session notice cannot be presented"); completionNotice2.Session = nonce; probe.ReceiveCompletionPayload(hostActive: true, connected: true, authenticated: true, currentConnection: true, completionNotice2); probe.ReceiveCompletionPayload(hostActive: false, connected: false, authenticated: true, currentConnection: true, completionNotice2); probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: false, currentConnection: true, completionNotice2); probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: false, completionNotice2); probe._clientAccepted = false; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 1 && probe._clientNoticeSequence == 1, "host loopback, disconnected, unauthenticated, replaced and unaccepted peers rejected"); probe._clientAccepted = true; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 2 && probe._clientNoticeSequence == 2, "next valid notice survives earlier callback fault"); completionNotice2.Sequence = 1L; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 2, "out-of-order notice rejected"); probe.Dispose(); completionNotice2.Sequence = 3L; probe.ReceiveCompletionPayload(hostActive: false, connected: true, authenticated: true, currentConnection: true, completionNotice2); Assert(delivered == 2 && probe.CompletionReceived == null, "disposed channel drops notices and releases callbacks"); } finally { if (probe != null) { ((IDisposable)probe).Dispose(); } } } private static void ExpectBadCompletionPacket(byte[] bytes, string description) { //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown bool condition = false; try { ReadCompletion(new NetworkReader(new ArraySegment(bytes))); } catch (InvalidDataException) { condition = true; } catch (DecoderFallbackException) { condition = true; } catch (EndOfStreamException) { condition = true; } Assert(condition, description); } public static string OfflineHandlerSelfTest() { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Expected O, but got Unknown //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Expected O, but got Unknown try { OwnedHandler ownedHandler = new OwnedHandler(null, delegate { }); Assert(!ownedHandler.HasServerHandler, "completion channel has no guest-to-host handler"); ushort id = NetworkMessages.GetId(); Dictionary table = new Dictionary(); NetworkMessageDelegate val = (NetworkMessageDelegate)delegate { }; NetworkMessageDelegate val2 = (NetworkMessageDelegate)delegate { }; table[id] = val; int removals = 0; Action unregister = delegate { removals++; table.Remove(id); }; OwnedHandler.RemoveIfOwned(table, id, val, unregister); Assert(removals == 1 && !table.ContainsKey(id), "owned handler removed on disposal"); OwnedHandler.RemoveIfOwned(table, id, val, unregister); Assert(removals == 1, "repeated disposal does not unregister another handler"); table[id] = val2; OwnedHandler.RemoveIfOwned(table, id, val, unregister); Assert(removals == 1 && object.ReferenceEquals(table[id], val2), "disposal preserves another owner's replacement handler"); return "PASS: pure client-only channel shape and handler disposal/replacement ownership; live registration not started"; } catch (Exception ex) { return "FAIL: completion handler regression: " + ex.GetType().Name + ": " + ex.Message; } } private static T RoundTrip(T message) where T : struct, NetworkMessage { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Expected O, but got Unknown NetworkWriter val = new NetworkWriter(); val.Write(message); NetworkReader val2 = new NetworkReader(new ArraySegment(val.ToArray())); T result = val2.Read(); Assert(val2.Remaining == 0, "message consumed completely"); return result; } private static void Assert(bool condition, string test) { if (!condition) { throw new InvalidDataException(test); } } public void Dispose() { DisposeStatsChannel(); DisposeGunChannel(); DisposeBailChannel(); if (!_disposed) { _disposed = true; if (_helloHandler != null) { _helloHandler.Dispose(); } if (_actionHandler != null) { _actionHandler.Dispose(); } if (_ackHandler != null) { _ackHandler.Dispose(); } if (_snapshotHandler != null) { _snapshotHandler.Dispose(); } if (_completionHandler != null) { _completionHandler.Dispose(); } for (int num = _serializers.Count - 1; num >= 0; num--) { _serializers[num].Dispose(); } _serializers.Clear(); _peers.Clear(); this.HostAction = null; this.HostVote = null; this.CompletionReceived = null; _clock.Stop(); } } private static void WriteText(NetworkWriter writer, string value, int maxBytes) { if (value == null) { value = ""; } if (value.Length > maxBytes || Utf8.GetByteCount(value) > maxBytes) { throw new InvalidDataException("Chaos Night string is too large"); } byte[] bytes = Utf8.GetBytes(value); if (bytes.Length > maxBytes) { throw new InvalidDataException("Chaos Night string is too large"); } NetworkWriterExtensions.WriteInt(writer, bytes.Length); writer.WriteBytes(bytes, 0, bytes.Length); } private static string ReadText(NetworkReader reader, int maxBytes) { int num = NetworkReaderExtensions.ReadInt(reader); if (num < 0 || num > maxBytes || num > reader.Remaining) { throw new InvalidDataException("Invalid Chaos Night string length"); } if (num == 0) { return ""; } return Utf8.GetString(NetworkReaderExtensions.ReadBytes(reader, num)); } private static void WriteHello(NetworkWriter writer, HelloMessage message) { NetworkWriterExtensions.WriteInt(writer, message.Protocol); WriteText(writer, message.Catalog, 64); WriteText(writer, message.Version, 32); WriteText(writer, message.ClientNonce, 32); } private static HelloMessage ReadHello(NetworkReader reader) { return new HelloMessage { Protocol = NetworkReaderExtensions.ReadInt(reader), Catalog = ReadText(reader, 64), Version = ReadText(reader, 32), ClientNonce = ReadText(reader, 32) }; } private static void WriteAck(NetworkWriter writer, AckMessage message) { writer.WriteByte((byte)(message.Accepted ? 1 : 0)); NetworkWriterExtensions.WriteInt(writer, message.Protocol); WriteText(writer, message.Catalog, 64); WriteText(writer, message.Version, 32); WriteText(writer, message.ClientNonce, 32); WriteText(writer, message.HostNonce, 32); WriteText(writer, message.Reason, 192); } private static AckMessage ReadAck(NetworkReader reader) { AckMessage result = default(AckMessage); byte b = reader.ReadByte(); if (b > 1) { throw new InvalidDataException("Invalid Chaos Night acceptance flag"); } result.Accepted = b == 1; result.Protocol = NetworkReaderExtensions.ReadInt(reader); result.Catalog = ReadText(reader, 64); result.Version = ReadText(reader, 32); result.ClientNonce = ReadText(reader, 32); result.HostNonce = ReadText(reader, 32); result.Reason = ReadText(reader, 192); return result; } private static void WriteSnapshot(NetworkWriter writer, SnapshotMessage message) { WriteText(writer, message.HostNonce, 32); writer.WriteByte((byte)(message.RoomReady ? 1 : 0)); WriteText(writer, message.Payload, 16384); } private static SnapshotMessage ReadSnapshot(NetworkReader reader) { SnapshotMessage result = new SnapshotMessage { HostNonce = ReadText(reader, 32) }; byte b = reader.ReadByte(); if (b > 1) { throw new InvalidDataException("Invalid Chaos Night readiness flag"); } result.RoomReady = b == 1; result.Payload = ReadText(reader, 16384); return result; } private static void WriteAction(NetworkWriter writer, ActionMessage message) { WriteText(writer, message.HostNonce, 32); NetworkWriterExtensions.WriteInt(writer, message.Sequence); NetworkWriterExtensions.WriteInt(writer, message.Action); NetworkWriterExtensions.WriteInt(writer, message.Index); NetworkWriterExtensions.WriteInt(writer, message.VoteId); WriteText(writer, message.VoteEpoch, 32); } private static ActionMessage ReadAction(NetworkReader reader) { return new ActionMessage { HostNonce = ReadText(reader, 32), Sequence = NetworkReaderExtensions.ReadInt(reader), Action = NetworkReaderExtensions.ReadInt(reader), Index = NetworkReaderExtensions.ReadInt(reader), VoteId = NetworkReaderExtensions.ReadInt(reader), VoteEpoch = ReadText(reader, 32) }; } private static void WriteCompletion(NetworkWriter writer, CompletionMessage message) { if (!ValidCompletion(message.Notice)) { throw new InvalidDataException("Invalid Chaos Night completion notice"); } int position = writer.Position; CompletionNotice notice = message.Notice; WriteText(writer, notice.Session, 32); NetworkWriterExtensions.WriteLong(writer, notice.Sequence); WriteText(writer, notice.Task, 128); WriteText(writer, notice.Actor, 96); WriteText(writer, notice.Amount, 64); writer.WriteByte((byte)(notice.Penalty ? 1 : 0)); if (writer.Position - position > 512) { throw new InvalidDataException("Completion notice exceeds its wire limit"); } } private static CompletionMessage ReadCompletion(NetworkReader reader) { int position = reader.Position; CompletionNotice completionNotice = new CompletionNotice(); completionNotice.Session = ReadText(reader, 32); completionNotice.Sequence = NetworkReaderExtensions.ReadLong(reader); completionNotice.Task = ReadText(reader, 128); completionNotice.Actor = ReadText(reader, 96); completionNotice.Amount = ReadText(reader, 64); CompletionNotice completionNotice2 = completionNotice; byte b = reader.ReadByte(); if (b > 1) { throw new InvalidDataException("Invalid Chaos Night completion flag"); } completionNotice2.Penalty = b == 1; if (reader.Position - position > 512 || !ValidCompletion(completionNotice2)) { throw new InvalidDataException("Invalid Chaos Night completion notice"); } return new CompletionMessage { Notice = completionNotice2 }; } private void InitializeBailChannel() { _serializers.Add(new OwnedSerializer(WriteBailContinue, ReadBailContinue)); _bailContinueHandler = new OwnedHandler(OnBailContinue, null); } private void DisposeBailChannel() { if (_bailContinueHandler != null) { _bailContinueHandler.Dispose(); } this.BailContinueRequested = null; } internal bool RequestBailContinue(long requestId, long revision) { if (!ReadyForChaos || requestId <= 0 || revision <= 0) { return false; } BailContinue bailContinue = new BailContinue { HostNonce = (NetworkServer.active ? _hostNonce : _acceptedHostNonce), RequestId = requestId, Revision = revision }; try { NetworkClient.Send(bailContinue, 0); return true; } catch (Exception ex) { Log("bail-send", "Bail acknowledgement delayed: " + ex.GetType().Name); return false; } } private void OnBailContinue(NetworkConnectionToClient connection, BailContinue request) { if (GunPeerAllowed(connection) && !(request.HostNonce != _hostNonce) && ValidBailContinue(request)) { this.BailContinueRequested?.Invoke(connection, request); } } private static bool ValidBailContinue(BailContinue request) { if (ValidNonce(request.HostNonce) && request.RequestId > 0 && request.Revision > 0 && request.RequestId < long.MaxValue) { return request.Revision < long.MaxValue; } return false; } private static void WriteBailContinue(NetworkWriter writer, BailContinue value) { if (!ValidBailContinue(value)) { throw new InvalidDataException("Invalid bail acknowledgement."); } WriteText(writer, value.HostNonce, 32); NetworkWriterExtensions.WriteLong(writer, value.RequestId); NetworkWriterExtensions.WriteLong(writer, value.Revision); } private static BailContinue ReadBailContinue(NetworkReader reader) { BailContinue bailContinue = new BailContinue { HostNonce = ReadText(reader, 32), RequestId = NetworkReaderExtensions.ReadLong(reader), Revision = NetworkReaderExtensions.ReadLong(reader) }; if (!ValidBailContinue(bailContinue)) { throw new InvalidDataException("Invalid bail acknowledgement."); } return bailContinue; } private void InitializeStatsChannel() { _serializers.Add(new OwnedSerializer(WriteStatsQuery, ReadStatsQuery)); _serializers.Add(new OwnedSerializer(WriteStatsReply, ReadStatsReply)); _serializers.Add(new OwnedSerializer(WriteStatsPaid, ReadStatsPaid)); _statsQueryHandler = new OwnedHandler(OnStatsQuery, null); _statsReplyHandler = new OwnedHandler(null, OnStatsReply); _statsPaidHandler = new OwnedHandler(null, OnStatsPaid); } private void DisposeStatsChannel() { if (_statsQueryHandler != null) { _statsQueryHandler.Dispose(); } if (_statsReplyHandler != null) { _statsReplyHandler.Dispose(); } if (_statsPaidHandler != null) { _statsPaidHandler.Dispose(); } _statsQueryRates.Clear(); this.StatsQueryReceived = null; this.StatsViewReceived = null; this.StatsPaidReceived = null; } private bool AcceptedStatsPeer(NetworkConnectionToClient connection) { if (!_disposed && NetworkServer.active && ReadyForChaos && connection != null && !IsLocal(connection) && ((NetworkConnection)connection).isAuthenticated && ((NetworkConnection)connection).isReady && IsLiveConnection(connection) && _peers.TryGetValue(connection.connectionId, out var value) && value.Accepted) { return object.ReferenceEquals(value.Connection, connection); } return false; } private bool AcceptedStatsHost(string nonce) { if (!_disposed && !NetworkServer.active && ReadyForChaos && ValidNonce(nonce) && nonce == _acceptedHostNonce && _clientConnection != null && ((NetworkConnection)_clientConnection).isAuthenticated) { return object.ReferenceEquals(_clientConnection, NetworkClient.connection); } return false; } public bool RequestStats(StatsQuery query) { if (query == null || query.Scope != 0 || query.RequestId <= 0 || NetworkServer.active || !ReadyForChaos) { return false; } try { NetworkClient.Send(new StatsQueryMessage { HostNonce = _acceptedHostNonce, RequestId = query.RequestId, Player = query.PlayerId }, 0); return true; } catch (Exception ex) { Log("stats-query", "Statistics request failed: " + ex.GetType().Name); return false; } } private void OnStatsQuery(NetworkConnectionToClient connection, StatsQueryMessage message) { if (!AcceptedStatsPeer(connection) || message.HostNonce != _hostNonce || message.RequestId <= 0) { return; } if (!_statsQueryRates.TryGetValue(connection.connectionId, out var value)) { if (_statsQueryRates.Count >= 128) { return; } value = new StatsQueryRate(); _statsQueryRates[connection.connectionId] = value; } double now = Now; if (!object.ReferenceEquals(value.Connection, connection) || !HoldLatestStatsQuery(value, message, now)) { value.Connection = connection; value.Next = now + 0.25; value.Pending = false; value.Latest = default(StatsQueryMessage); EmitStatsQuery(connection, message); } } private static bool HoldLatestStatsQuery(StatsQueryRate rate, StatsQueryMessage message, double now) { if (now >= rate.Next) { return false; } rate.Latest = message; rate.Pending = true; return true; } private static bool TakePendingStatsQuery(StatsQueryRate rate, double now, out StatsQueryMessage message) { message = default(StatsQueryMessage); if (!rate.Pending || now < rate.Next) { return false; } message = rate.Latest; rate.Pending = false; rate.Latest = default(StatsQueryMessage); rate.Next = now + 0.25; return true; } public void FlushStatsQueries() { if (_disposed || !NetworkServer.active || _statsQueryRates.Count == 0) { return; } double now = Now; List list = null; List list2 = null; foreach (KeyValuePair statsQueryRate in _statsQueryRates) { StatsQueryRate value = statsQueryRate.Value; StatsQueryMessage message; if (!IsLiveConnection(value.Connection)) { if (list == null) { list = new List(); } list.Add(statsQueryRate.Key); } else if (value.Pending && !(now < value.Next) && AcceptedStatsPeer(value.Connection) && TakePendingStatsQuery(value, now, out message)) { if (list2 == null) { list2 = new List(); } list2.Add(new StatsQueryDelivery { Connection = value.Connection, Message = message }); } } if (list != null) { foreach (int item in list) { _statsQueryRates.Remove(item); } } if (list2 == null) { return; } foreach (StatsQueryDelivery item2 in list2) { EmitStatsQuery(item2.Connection, item2.Message); } } private void EmitStatsQuery(NetworkConnectionToClient connection, StatsQueryMessage message) { if (!AcceptedStatsPeer(connection) || message.HostNonce != _hostNonce || message.RequestId <= 0) { return; } Action statsQueryReceived = this.StatsQueryReceived; if (statsQueryReceived == null) { return; } try { statsQueryReceived(new StatsQuery { RequestId = message.RequestId, Scope = 0, PlayerId = message.Player }, connection); } catch (Exception ex) { Log("stats-query-handler", "Statistics query held: " + ex.GetType().Name); } } public void ReplyStats(NetworkConnectionToClient connection, StatsView view) { if (!AcceptedStatsPeer(connection) || !ValidStatsView(view)) { return; } try { ((NetworkConnection)connection).Send(new StatsReplyMessage { HostNonce = _hostNonce, Payload = ChaosJson.Write(view) }, 0); } catch (Exception ex) { Log("stats-reply-send", "Statistics reply failed: " + ex.GetType().Name); } } public void SendStatsPaid(NetworkConnectionToClient connection, StatsSettlement paid) { if (!AcceptedStatsPeer(connection) || !ValidStatsSettlement(paid) || paid.Stream != _hostNonce) { return; } try { ((NetworkConnection)connection).Send(new StatsPaidMessage { HostNonce = _hostNonce, Payload = ChaosJson.Write(paid) }, 0); } catch (Exception ex) { Log("stats-paid-send", "Paid statistics delivery failed: " + ex.GetType().Name); } } private void OnStatsReply(StatsReplyMessage message) { if (!AcceptedStatsHost(message.HostNonce)) { return; } try { StatsView statsView = ChaosJson.Read(message.Payload); if (ValidStatsView(statsView) && statsView.Scope == 0) { this.StatsViewReceived?.Invoke(statsView); } } catch (Exception ex) { Log("stats-reply-read", "Invalid statistics reply ignored: " + ex.GetType().Name); } } private void OnStatsPaid(StatsPaidMessage message) { if (!AcceptedStatsHost(message.HostNonce)) { return; } try { StatsSettlement statsSettlement = ChaosJson.Read(message.Payload); if (ValidStatsSettlement(statsSettlement) && !(statsSettlement.Stream != message.HostNonce)) { this.StatsPaidReceived?.Invoke(statsSettlement); } } catch (Exception ex) { Log("stats-paid-read", "Invalid paid statistics ignored: " + ex.GetType().Name); } } internal static bool ValidStatsSettlement(StatsSettlement paid) { if (paid != null && ValidNonce(paid.Stream) && ValidNonce(paid.RunId) && StatsLedger.ValidId(paid.ReceiptId) && paid.ReceiptId == paid.Stream + ":" + paid.Sequence.ToString(CultureInfo.InvariantCulture) && paid.Sequence > 0 && paid.Player != 0 && paid.Game >= 0 && paid.Game < 17 && paid.PlayerName != null && paid.PlayerName == StatsLedger.CleanName(paid.PlayerName) && StatsAmount.IsValid(paid.Bet) && paid.Bet.Mantissa > 0.0 && StatsAmount.IsValid(paid.Payout)) { return paid.Payout.Mantissa >= 0.0; } return false; } internal static bool ValidStatsView(StatsView view) { if (view == null || view.RequestId <= 0 || view.Scope < 0 || view.Scope > 1 || view.LocalPlayerId == 0 || !StatsText(view.Status, 256) || !StatsText(view.PeriodLabel, 128) || !StatsText(view.PrivacyHint, 256) || view.Players == null || view.Players.Length > 32 || view.Games == null || view.Games.Length != 17 || !ValidStatsTotals(view.Summary)) { return false; } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); StatsPlayerOption[] players = view.Players; foreach (StatsPlayerOption statsPlayerOption in players) { if (statsPlayerOption == null || statsPlayerOption.Id == 0 || !hashSet.Add(statsPlayerOption.Id) || !StatsText(statsPlayerOption.Name, 64) || (statsPlayerOption.LifetimeAvailable && statsPlayerOption.Id != view.LocalPlayerId)) { return false; } } StatsGameView[] games = view.Games; foreach (StatsGameView statsGameView in games) { if (statsGameView == null || statsGameView.GameId < 0 || statsGameView.GameId >= 17 || !hashSet2.Add(statsGameView.GameId) || !StatsText(statsGameView.Name, 64) || !ValidStatsTotals(statsGameView.Totals)) { return false; } } if (view.Scope == 1) { return view.PlayerId == view.LocalPlayerId; } return true; } private static bool StatsText(string value, int max) { if (value != null && value.Length <= max) { return value.IndexOf('\0') < 0; } return false; } private static bool ValidStatsTotals(StatsTotals totals) { if (totals != null && totals.Played >= 0 && totals.Wins >= 0 && totals.Losses >= 0 && totals.Ties >= 0 && totals.Wins <= totals.Played && totals.Losses <= totals.Played - totals.Wins && totals.Ties == totals.Played - totals.Wins - totals.Losses && totals.BigWins >= 0 && totals.BigWins <= totals.Wins && StatsText(totals.Net, 64) && StatsText(totals.PositiveNet, 64) && StatsText(totals.NegativeNet, 64) && StatsText(totals.Wagered, 64) && StatsText(totals.Payout, 64) && StatsText(totals.BestNetWin, 64) && StatsText(totals.LargestBet, 64) && StatsText(totals.WinRate, 32)) { return StatsText(totals.BigWinRate, 32); } return false; } private static void WriteStatsQuery(NetworkWriter w, StatsQueryMessage m) { WriteText(w, m.HostNonce, 32); NetworkWriterExtensions.WriteInt(w, m.RequestId); NetworkWriterExtensions.WriteULong(w, m.Player); } private static StatsQueryMessage ReadStatsQuery(NetworkReader r) { return new StatsQueryMessage { HostNonce = ReadText(r, 32), RequestId = NetworkReaderExtensions.ReadInt(r), Player = NetworkReaderExtensions.ReadULong(r) }; } private static void WriteStatsReply(NetworkWriter w, StatsReplyMessage m) { WriteText(w, m.HostNonce, 32); WriteText(w, m.Payload, 32768); } private static StatsReplyMessage ReadStatsReply(NetworkReader r) { return new StatsReplyMessage { HostNonce = ReadText(r, 32), Payload = ReadText(r, 32768) }; } private static void WriteStatsPaid(NetworkWriter w, StatsPaidMessage m) { WriteText(w, m.HostNonce, 32); WriteText(w, m.Payload, 1024); } private static StatsPaidMessage ReadStatsPaid(NetworkReader r) { return new StatsPaidMessage { HostNonce = ReadText(r, 32), Payload = ReadText(r, 1024) }; } } } namespace ChaosNight.Core { public sealed class GunMagazine { public const int Capacity = 10; public const double ReloadSeconds = 1.5; public const double ReservationSeconds = 0.45; private uint shotOwner; private double shotUntil; private int routes; public int Ammo { get; private set; } public bool Reloading { get; private set; } public double ReloadUntil { get; private set; } public double NextShot { get; private set; } public uint Owner { get; private set; } public GunMagazine(int ammo) { if (ammo < 0 || ammo > 10) { throw new ArgumentOutOfRangeException("ammo"); } Ammo = ammo; } public void Bind(uint owner) { if (owner != Owner) { CancelReload(); InvalidateShot(); Owner = owner; } } public bool Reserve(uint owner, double now, double cooldown) { if (owner == 0 || owner != Owner || !Finite(now) || !Finite(cooldown) || cooldown < 0.05 || cooldown > 10.0 || Reloading || Ammo <= 0 || now < NextShot) { return false; } Ammo--; NextShot = now + cooldown; shotOwner = owner; shotUntil = now + Math.Min(0.45, cooldown * 0.9); routes = 0; return true; } public bool TakeRoute(uint owner, double now, int route) { if (owner == 0 || owner != Owner || owner != shotOwner || !Finite(now) || now > shotUntil || Reloading || (route != 1 && route != 2 && route != 4) || (routes & route) != 0) { return false; } if ((routes & 4) != 0) { return false; } routes |= route; return true; } public bool BeginReload(uint owner, double now) { if (owner == 0 || owner != Owner || !Finite(now) || Reloading || Ammo >= 10) { return false; } Reloading = true; ReloadUntil = now + 1.5; InvalidateShot(); return true; } public bool CompleteReload(uint owner, double now) { if (!Reloading || owner == 0 || owner != Owner || !Finite(now) || now < ReloadUntil) { return false; } Ammo = 10; Reloading = false; ReloadUntil = 0.0; InvalidateShot(); return true; } public void CancelReload() { Reloading = false; ReloadUntil = 0.0; } public void InvalidateShot() { shotOwner = 0u; shotUntil = 0.0; routes = 0; } public static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } public static bool AcceptSequence(int sequence, int previous) { if (sequence > 0) { return sequence > previous; } return false; } public static bool AcceptReloadSnapshot(int sequence, int pendingReload) { if (pendingReload > 0) { return sequence >= pendingReload; } return true; } public static double Motion(double progress, bool reduced) { if (!reduced && Finite(progress) && !(progress <= 0.0) && !(progress >= 1.0)) { return Math.Sin(progress * Math.PI); } return 0.0; } public static double HudScale(int height) { return Math.Max(0.7, Math.Min(1.5, (double)height / 900.0)); } public static int AmmoFont(double scale) { return Math.Max(18, (int)Math.Round(27.0 * scale)); } public static int SmallFont(double scale) { return Math.Max(11, (int)Math.Round(13.0 * scale)); } } public sealed class GunClientMagazine { public const double SpendSeconds = 0.34; private readonly List pending = new List(); private readonly double[] spent = new double[10]; private int minimumSequence; public int Ammo { get; private set; } public int AuthoritativeAmmo { get; private set; } public int Acknowledged { get; private set; } public bool Synced { get; private set; } public int PendingCount => pending.Count; public GunClientMagazine() { Reset(1); } public void Reset(int firstSequence) { pending.Clear(); ClearSpend(); int ammo = (AuthoritativeAmmo = 0); Ammo = ammo; Synced = false; minimumSequence = firstSequence; } private void ClearSpend() { for (int i = 0; i < spent.Length; i++) { spent[i] = double.NegativeInfinity; } } public bool Predict(int sequence) { if (!Synced || Ammo <= 0 || sequence <= Acknowledged || sequence < minimumSequence || pending.Count >= 10 || pending.Contains(sequence)) { return false; } pending.Add(sequence); Ammo--; return true; } public void CancelPrediction(int sequence) { pending.Remove(sequence); Ammo = Math.Max(0, AuthoritativeAmmo - pending.Count); } public bool Snapshot(int sequence, int authoritativeAmmo, int acceptedShot, double now) { if (sequence <= 0 || sequence < minimumSequence || sequence < Acknowledged || authoritativeAmmo < 0 || authoritativeAmmo > 10 || !GunMagazine.Finite(now) || (acceptedShot != 0 && acceptedShot != sequence)) { return false; } bool flag = acceptedShot != 0 && pending.Contains(acceptedShot); if (authoritativeAmmo > AuthoritativeAmmo) { ClearSpend(); } if (flag && authoritativeAmmo < AuthoritativeAmmo) { spent[authoritativeAmmo] = now; } Acknowledged = sequence; AuthoritativeAmmo = authoritativeAmmo; pending.RemoveAll((int s) => s <= sequence); Ammo = Math.Max(0, authoritativeAmmo - pending.Count); Synced = true; return true; } public double SpendProgress(int icon, double now) { if (icon < 0 || icon >= spent.Length || !GunMagazine.Finite(now)) { return -1.0; } double num = (now - spent[icon]) / 0.34; if (!(num >= 0.0) || !(num < 1.0)) { return -1.0; } return num; } } } namespace ChaosNight { [Serializable] internal sealed class GunAmmoFile { public int Schema = 2; public string Save; public List Entries = new List(); } [Serializable] internal sealed class GunAmmoEntry { public string Slot; public int Ammo; } internal sealed class GunAmmoJournal { private readonly string directory; private string save; private string path; private readonly Dictionary values = new Dictionary(); public GunAmmoJournal(string root) { directory = Path.Combine(root, "ChaosNight", "magazines"); } public void Select(string identity) { if (string.IsNullOrEmpty(identity) || identity.Length > 512) { throw new InvalidDataException("Native gun save identity unavailable."); } if (identity == save) { return; } values.Clear(); save = null; this.path = Path.Combine(directory, ChaosSave.Hash(identity) + ".json"); if (File.Exists(this.path)) { FileInfo fileInfo = new FileInfo(this.path); if (fileInfo.Length > 65536) { throw new InvalidDataException("Magazine companion exceeds its limit."); } GunAmmoFile gunAmmoFile = ChaosJson.Read(File.ReadAllText(this.path)); if (gunAmmoFile == null || (gunAmmoFile.Schema != 1 && gunAmmoFile.Schema != 2) || gunAmmoFile.Save != identity || gunAmmoFile.Entries == null || gunAmmoFile.Entries.Count > 512) { throw new InvalidDataException("Magazine companion does not match the native save."); } foreach (GunAmmoEntry entry in gunAmmoFile.Entries) { if (entry == null || string.IsNullOrEmpty(entry.Slot) || entry.Slot.Length > 64 || entry.Ammo < 0 || entry.Ammo > ((gunAmmoFile.Schema == 1) ? 15 : 10) || values.ContainsKey(entry.Slot)) { throw new InvalidDataException("Magazine companion contains invalid entries."); } values.Add(entry.Slot, Math.Min(entry.Ammo, 10)); } if (gunAmmoFile.Schema == 1) { GunAmmoFile gunAmmoFile2 = new GunAmmoFile(); gunAmmoFile2.Save = identity; GunAmmoFile gunAmmoFile3 = gunAmmoFile2; foreach (string item in values.Keys.OrderBy((string k) => k, StringComparer.Ordinal)) { gunAmmoFile3.Entries.Add(new GunAmmoEntry { Slot = item, Ammo = values[item] }); } string path = Path.Combine(directory, "recovery", "capacity-" + 10 + "-" + DateTime.UtcNow.ToString("yyyyMMddTHHmmssfffZ") + "-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(path); File.Copy(this.path, Path.Combine(path, "magazines.v1.json"), overwrite: false); ReplaceFile(this.path, ChaosJson.Write(gunAmmoFile3)); } } save = identity; } public int Read(string slot, int owned) { if (save == null || string.IsNullOrEmpty(slot) || owned <= 0 || owned > 512) { throw new InvalidOperationException("Magazine save or ownership not selected."); } string text = slot.Substring(0, slot.IndexOf(':') + 1); int num = 10; int num2 = -1; foreach (KeyValuePair value2 in values) { if (value2.Key.StartsWith(text, StringComparison.Ordinal)) { if (!int.TryParse(value2.Key.Substring(text.Length), out var result) || result < 0 || result >= 512) { throw new InvalidDataException("Invalid purchased gun ordinal."); } num = Math.Min(num, value2.Value); num2 = Math.Max(num2, result); } } if (num2 >= owned) { return num; } if (!values.TryGetValue(slot, out var value)) { return 10; } return value; } public void Commit(string slot, int ammo) { if (save == null || string.IsNullOrEmpty(slot) || slot.Length > 64 || ammo < 0 || ammo > 10 || (!values.ContainsKey(slot) && values.Count >= 512)) { throw new InvalidDataException("Invalid magazine commit."); } GunAmmoFile gunAmmoFile = new GunAmmoFile(); gunAmmoFile.Save = save; GunAmmoFile gunAmmoFile2 = gunAmmoFile; foreach (string item in values.Keys.OrderBy((string k) => k, StringComparer.Ordinal)) { gunAmmoFile2.Entries.Add(new GunAmmoEntry { Slot = item, Ammo = ((item == slot) ? ammo : values[item]) }); } if (!values.ContainsKey(slot)) { gunAmmoFile2.Entries.Add(new GunAmmoEntry { Slot = slot, Ammo = ammo }); } string text = ChaosJson.Write(gunAmmoFile2); if (Encoding.UTF8.GetByteCount(text) > 65536) { throw new InvalidDataException("Magazine companion exceeds its limit."); } Directory.CreateDirectory(directory); string sourceFileName = path + ".pending"; using (FileStream fileStream = new FileStream(sourceFileName, FileMode.Create, FileAccess.Write, FileShare.None)) { byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(text); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(path)) { File.Replace(sourceFileName, path, path + ".previous", ignoreMetadataErrors: true); } else { File.Move(sourceFileName, path); } if (File.ReadAllText(path) != text) { throw new IOException("Magazine readback failed."); } values[slot] = ammo; } public Action CaptureRecovery() { if (save == null) { throw new InvalidOperationException("Magazine recovery needs the selected native save."); } string originalSave = save; string originalPath = path; string original = (File.Exists(path) ? File.ReadAllText(path) : ChaosJson.Write(new GunAmmoFile { Save = save })); return delegate { ReplaceFile(originalPath, original); save = null; Select(originalSave); }; } public void StartFresh(string identity) { Select(identity); if (File.Exists(this.path)) { string path = Path.Combine(directory, "recovery", DateTime.UtcNow.ToString("yyyyMMddTHHmmssfffZ") + "-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(path); File.Copy(this.path, Path.Combine(path, "magazines.json"), overwrite: false); if (File.Exists(this.path + ".previous")) { File.Copy(this.path + ".previous", Path.Combine(path, "magazines.previous.json"), overwrite: false); } } ReplaceFile(this.path, ChaosJson.Write(new GunAmmoFile { Save = identity })); values.Clear(); } private static void ReplaceFile(string destination, string json) { Directory.CreateDirectory(Path.GetDirectoryName(destination)); string sourceFileName = destination + ".pending"; using (FileStream fileStream = new FileStream(sourceFileName, FileMode.Create, FileAccess.Write, FileShare.None)) { byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(json); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (File.Exists(destination)) { File.Replace(sourceFileName, destination, destination + ".previous", ignoreMetadataErrors: true); } else { File.Move(sourceFileName, destination); } if (File.ReadAllText(destination) != json) { throw new IOException("Magazine recovery readback failed."); } } } public static class GunShopPolicy { public const long Price = 1L; public const int MaximumParty = 32; public const float MinimumInterval = 0.3f; public const float PurchasePopupSeconds = 3f; public const float MaximumDistance = 3.5f; public const int MaximumAnchorDepth = 16; public const float PlacementRetrySeconds = 2f; public const string SignPath = "Model/Signs/SM_Misfortune"; public const string BusPath = "Model/Base/SM_ItemStoreTruck"; public const int MaximumBusColliders = 64; public const int SupportPoints = 5; public const double MinimumGroundNormal = 0.995; public const double MaximumGroundSpread = 0.04; public const float GroundLift = 0.03f; public const float ClearancePadding = 0.04f; public const float BusGap = 0.08f; public static bool TrySupportHeight(IList heights, IList normals, out double highest) { highest = 0.0; if (heights == null || normals == null || heights.Count != 5 || normals.Count != 5) { return false; } double num = double.PositiveInfinity; double num2 = double.NegativeInfinity; for (int i = 0; i < 5; i++) { if (!Finite(heights[i]) || !Finite(normals[i]) || normals[i] < 0.995 || normals[i] > 1.000001) { return false; } num = Math.Min(num, heights[i]); num2 = Math.Max(num2, heights[i]); } if (num2 - num > 0.04) { return false; } highest = num2; return true; } public static double ProjectedExtent(double axisX, double axisZ, double extentX, double extentZ) { if (!Finite(axisX) || !Finite(axisZ) || !Finite(extentX) || !Finite(extentZ) || extentX < 0.0 || extentZ < 0.0) { return double.NaN; } return Math.Abs(axisX) * extentX + Math.Abs(axisZ) * extentZ; } public static bool OverlapsSignSlice(double lateralCentre, double projectedExtent, double halfWidth) { if (Finite(lateralCentre) && Finite(projectedExtent) && Finite(halfWidth) && projectedExtent >= 0.0 && halfWidth > 0.0) { return Math.Abs(lateralCentre) <= projectedExtent + halfWidth; } return false; } public static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } public static bool IsItemShopHierarchy(IList ancestors) { if (ancestors == null) { return false; } for (int i = 0; i < ancestors.Count && i < 16; i++) { if (string.Equals(ancestors[i], "ItemShop", StringComparison.Ordinal) || string.Equals(ancestors[i], "ItemShop(Clone)", StringComparison.Ordinal)) { return true; } } return false; } public static bool CanBuy(bool enabled, bool server, bool homeReady, bool senderOwned, bool unlocked, long tickets, double distance, float now, float nextAllowed) { if (enabled && server && homeReady && senderOwned && unlocked && tickets >= 1 && !double.IsNaN(distance) && !double.IsInfinity(distance) && distance >= 0.0 && distance <= 3.5 && Finite(now) && Finite(nextAllowed) && now >= 0f && nextAllowed >= 0f) { return now >= nextAllowed; } return false; } public static bool ExactCharge(long before, long after) { if (before >= 1 && after >= 0) { return before - after == 1; } return false; } public static bool ExactAppend(IList list, int index, T value) where T : class { if (list != null && index >= 0 && index < list.Count) { return object.ReferenceEquals(list[index], value); } return false; } public static bool CanPruneCasinoInstances(bool enabled, bool server, bool game, bool casino, bool currentItems, bool currentElevator, bool listAvailable) { if (enabled && server && game && casino && currentItems && currentElevator) { return listAvailable; } return false; } public static int PruneDestroyedInstances(IList list, Func isAlive) where T : class { if (list == null || isAlive == null) { return 0; } int num = 0; for (int num2 = list.Count - 1; num2 >= 0; num2--) { if (object.ReferenceEquals(list[num2], null) || !isAlive(list[num2])) { list.RemoveAt(num2); num++; } } return num; } public static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } } public sealed class ChaosGunShopMarker : MonoBehaviour { } public sealed class ChaosGunShop : IDisposable { private sealed class Attempt { public NetworkConnectionToClient Connection; public float Next; } public const uint VendorAssetId = 1129203539u; internal static ChaosGunShop Active; private readonly Func canUse; private readonly Func canGuardNativeItems; private readonly Func> readyParty; private readonly Action merchantScope; private readonly Action compensated; private readonly Action purchased; private readonly Action log; private readonly HashSet errors = new HashSet(); private readonly Dictionary attempts = new Dictionary(); private readonly RaycastHit[] hits = (RaycastHit[])(object)new RaycastHit[24]; private readonly double[] supportHeights = new double[5]; private readonly double[] supportNormals = new double[5]; private readonly List anchorHierarchy = new List(16); private GameObject template; private GameObject vendor; private InteractableEventTrigger vendorInteract; private BoxCollider vendorCollider; private SpawnableSO gun; private MerchantBuyButton anchor; private Transform itemShopRoot; private Transform busAnchor; private Transform signAnchor; private Renderer signRenderer; private Collider[] busColliders; private int observedScene = -1; private float nextTick; private float nextPlacement; private bool disposed; private bool buying; private string lastHold; private static readonly FieldInfo ManagerInstance = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly FieldInfo ItemInstance = AccessTools.Field(typeof(ItemManager), "_instance"); private static readonly FieldInfo MoneyInstance = AccessTools.Field(typeof(MoneyManager), "_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 InteractLocked = AccessTools.Field(typeof(PlayerInteract), "_isLocked"); private static readonly FieldInfo SpawnHandlers = AccessTools.Field(typeof(NetworkClient), "spawnHandlers"); private static readonly FieldInfo UnspawnHandlers = AccessTools.Field(typeof(NetworkClient), "unspawnHandlers"); public ChaosGunShop(Func canUseNativeFeatures, Func> readyNativeParty, Action purchaseScope, Action logger) : this(canUseNativeFeatures, readyNativeParty, purchaseScope, logger, null) { } public ChaosGunShop(Func canUseNativeFeatures, Func> readyNativeParty, Action purchaseScope, Action logger, Action compensatedPurchase, Func nativeItemLifecycleGuard = null, Action purchaseConfirmed = null) { if (canUseNativeFeatures == null || readyNativeParty == null || purchaseScope == null) { throw new ArgumentNullException("Native gun shop guards"); } if (Active != null) { throw new InvalidOperationException("Native gun shop already initialized."); } if (!NativeApiAvailable()) { throw new InvalidOperationException("Verified native gun shop API is unavailable."); } canUse = canUseNativeFeatures; readyParty = readyNativeParty; merchantScope = purchaseScope; log = logger; canGuardNativeItems = nativeItemLifecycleGuard ?? canUseNativeFeatures; compensated = compensatedPurchase; purchased = purchaseConfirmed; try { FindGun(); template = BuildTemplate(); if (!EnsureRegistered()) { throw new InvalidOperationException("Gun stand prefab ID is unavailable."); } Active = this; } catch { if ((Object)(object)template != (Object)null) { Object.Destroy((Object)(object)template); } throw; } } public void Tick() { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0066: Unknown result type (might be due to invalid IL or missing references) //IL_006d: 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_016e: Unknown result type (might be due to invalid IL or missing references) //IL_01f4: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) if (disposed || Time.unscaledTime < nextTick) { return; } nextTick = Time.unscaledTime + 0.5f; try { if (!NetworkServer.active) { vendor = null; vendorInteract = null; vendorCollider = null; attempts.Clear(); observedScene = -1; return; } Scene activeScene = SceneManager.GetActiveScene(); if (SceneHandle.op_Implicit(observedScene) != ((Scene)(ref activeScene)).handle) { DestroyVendor(); attempts.Clear(); anchor = null; ClearBusAnchors(); lastHold = null; observedScene = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle); nextPlacement = 0f; } object? value = ManagerInstance.GetValue(null); GameManager manager = (GameManager)((value is GameManager) ? value : null); if (((Scene)(ref activeScene)).name != "HomeScene") { DestroyVendor(); Hold("Gun stand is available only in the Home item shop."); return; } if (!FeaturesEnabled()) { DestroyVendor(); Hold("Gun stand waiting: host compatibility or game-state readiness is held."); return; } if (!TryParty(out var party)) { DestroyVendor(); Hold("Gun stand waiting: all current players must be authenticated, spawned and ready."); return; } if (!HomeReady(manager, party)) { DestroyVendor(); Hold("Gun stand waiting: native Home loading and player scene readiness are not complete."); return; } PruneAttempts(party); if ((Object)(object)anchor != (Object)null && !IsItemShopAnchor(anchor, activeScene)) { DestroyVendor(); anchor = null; ClearBusAnchors(); } if ((Object)(object)vendor != (Object)null || Time.unscaledTime < nextPlacement) { return; } nextPlacement = Time.unscaledTime + 2f; if (!EnsureRegistered()) { return; } if ((Object)(object)gun == (Object)null) { FindGun(); } if (!GunRegistered()) { Hold("Gun stand waiting: the native gun prefab is not registered yet."); return; } FindAnchor(activeScene); if ((Object)(object)anchor == (Object)null) { Hold("Gun stand waiting: the native ItemShop checkout is not available yet; retrying."); return; } if (!CacheBusAnchors()) { Hold("Gun stand waiting: the native ItemShop bus and Misfortune sign are not available yet; retrying."); return; } if (!TryPlacement(out var position, out var rotation)) { Hold("Gun stand waiting: no flat, unobstructed ground beneath the ItemShop Misfortune sign; retrying."); return; } GameObject val = Object.Instantiate(template, position, rotation); try { ((Object)val).name = "Chaos Night Gun Stand"; SceneManager.MoveGameObjectToScene(val, activeScene); val.SetActive(true); NetworkServer.Spawn(val, (NetworkConnectionToClient)null); if (!ServerSpawned(val.GetComponent())) { throw new InvalidOperationException("Native server did not register the gun stand."); } vendor = val; vendorInteract = val.GetComponent(); vendorCollider = val.GetComponent(); lastHold = null; Write("Native gun stand available beneath ItemShop/Model/Signs/SM_Misfortune at " + ((Vector3)(ref position)).ToString("F2") + ": 1 ticket, unlimited stock."); } catch { DestroySpawn(val); throw; } } catch (Exception error) { Report("gun stand lifecycle", error); } } internal bool EnsureRegistered() { if (disposed || (Object)(object)template == (Object)null) { return false; } try { if (NetworkClient.prefabs.TryGetValue(1129203539u, out var value)) { return object.ReferenceEquals(value, template) && !HasHandler(1129203539u); } if (HasHandler(1129203539u)) { throw new InvalidOperationException("Gun stand asset ID conflicts with a spawn handler."); } NetworkClient.RegisterPrefab(template, 1129203539u); return NetworkClient.prefabs.TryGetValue(1129203539u, out value) && object.ReferenceEquals(value, template); } catch (Exception error) { Report("gun stand registration", error); return false; } } internal bool IsOwnVendor(NetworkBehaviour target) { if (!disposed && (Object)(object)vendor != (Object)null && (Object)(object)vendorInteract != (Object)null) { return object.ReferenceEquals(target, vendorInteract); } return false; } internal void Purchase(InteractableEventTrigger target, PlayerInteract actor) { //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_021b: Unknown result type (might be due to invalid IL or missing references) if (disposed || buying || !NetworkServer.active || !IsOwnVendor((NetworkBehaviour)(object)target)) { return; } try { GunShopCommandContext current = GunShopCommandContext.Current; if (current == null || !object.ReferenceEquals(current.Target, target) || current.Sender == null) { return; } object? value = ManagerInstance.GetValue(null); GameManager manager = (GameManager)((value is GameManager) ? value : null); if (!FeaturesEnabled() || !TryParty(out var party) || !HomeReady(manager, party) || !party.Contains(current.Sender) || !ActorOwned(current.Sender, actor) || !ServerSpawned(((NetworkBehaviour)target).netIdentity)) { return; } Scene scene = ((Component)target).gameObject.scene; SceneHandle handle = ((Scene)(ref scene)).handle; Scene activeScene = SceneManager.GetActiveScene(); if (handle != ((Scene)(ref activeScene)).handle) { return; } float unscaledTime = Time.unscaledTime; if (!attempts.TryGetValue(current.Sender.connectionId, out var value2) || !object.ReferenceEquals(value2.Connection, current.Sender)) { Attempt attempt = new Attempt(); attempt.Connection = current.Sender; attempt.Next = 0f; value2 = attempt; attempts[current.Sender.connectionId] = value2; } Vector3 val = ((Component)actor).transform.position + Vector3.up * 1.3f; double distance = (((Object)(object)vendorCollider == (Object)null) ? double.PositiveInfinity : ((double)Vector3.Distance(val, ((Collider)vendorCollider).ClosestPoint(val)))); PlayerController component = ((Component)actor).GetComponent(); object? value3 = MoneyInstance.GetValue(null); MoneyManager val2 = (MoneyManager)((value3 is MoneyManager) ? value3 : null); bool unlocked = (Object)(object)component != (Object)null && !component.IsLocked && !(bool)InteractLocked.GetValue(actor); if (unscaledTime < value2.Next || !GunShopPolicy.Finite(unscaledTime)) { return; } value2.Next = unscaledTime + 0.3f; if (!GunShopPolicy.CanBuy(enabled: true, server: true, homeReady: true, senderOwned: true, unlocked, ((Object)(object)val2 == (Object)null) ? 0 : val2.ticketBalance, distance, unscaledTime, unscaledTime) || !LineOfSight(actor, val)) { return; } object? value4 = ItemInstance.GetValue(null); ItemManager val3 = (ItemManager)((value4 is ItemManager) ? value4 : null); PlayerProfile component2 = ((Component)((NetworkConnection)current.Sender).identity).GetComponent(); if ((Object)(object)val3 == (Object)null || !((NetworkBehaviour)val3).isServer || val3.currentItems == null || (Object)(object)val2 == (Object)null || !((NetworkBehaviour)val2).isServer || !GunRegistered() || (Object)(object)component2 == (Object)null || !((NetworkBehaviour)component2).isServer || component2.steamId == 0 || !object.ReferenceEquals(((NetworkBehaviour)component2).netIdentity, ((NetworkConnection)current.Sender).identity)) { return; } buying = true; try { Grant(val3, val2, component2); } finally { buying = false; } } catch (Exception error) { Report("gun purchase", error); } } private void Grant(ItemManager items, MoneyManager money, PlayerProfile purchaser) { long ticketBalance = money.ticketBalance; if (ticketBalance < 1) { return; } int count = items.currentItems.Count; bool flag = false; bool flag2 = false; try { QuotaGun component = gun.prefab.GetComponent(); if ((Object)(object)component == (Object)null || !object.ReferenceEquals(((Item)component).spawnableSo, gun)) { throw new InvalidOperationException("Gun prefab does not match its native catalog entry."); } items.ServerAddItem(gun); if (!GunShopPolicy.ExactAppend(items.currentItems, count, gun)) { throw new InvalidOperationException("Native item ownership was not recorded."); } flag = true; merchantScope(obj: true); try { if (!money.TryChangeTicketBalance(-1L)) { return; } if (!GunShopPolicy.ExactCharge(ticketBalance, money.ticketBalance)) { throw new InvalidOperationException("Native ticket charge did not equal one ticket."); } flag2 = true; if (purchased != null) { try { purchased(purchaser); } catch (Exception error) { Report("gun purchase confirmation", error); } } } finally { if (flag) { flag = false; merchantScope(obj: false); } } Write("Native gun purchased for 1 ticket; collect it free from the Casino elevator item guy. No gun spawned at Home; stock remains available."); } finally { if (flag) { try { merchantScope(obj: false); } catch (Exception error2) { Report("gun purchase scope", error2); } } if (!flag2) { if (GunShopPolicy.ExactAppend(items.currentItems, count, gun)) { items.currentItems.RemoveAt(count); } if (GunShopPolicy.ExactCharge(ticketBalance, money.ticketBalance)) { if (!money.TryChangeTicketBalance(1L) || money.ticketBalance != ticketBalance) { Report("gun ticket compensation", new InvalidOperationException("Exact native ticket refund was rejected.")); } else if (compensated != null) { try { compensated(); } catch (Exception error3) { Report("gun refunded purchase bookkeeping", error3); } } } } } } private bool TryParty(out List party) { party = null; if (!NetworkServer.active || NetworkServer.localConnection == null) { return false; } List list = readyParty(); if (list == null || list.Count == 0 || list.Count > 32 || list.Count != NetworkServer.connections.Count || !list.Contains((NetworkConnectionToClient)(object)NetworkServer.localConnection)) { return false; } HashSet hashSet = new HashSet(); foreach (NetworkConnectionToClient item in list) { if (item == null || !hashSet.Add(item.connectionId) || !((NetworkConnection)item).isAuthenticated || !((NetworkConnection)item).isReady || (Object)(object)((NetworkConnection)item).identity == (Object)null || !NetworkServer.connections.TryGetValue(item.connectionId, out var value) || !object.ReferenceEquals(value, item) || !object.ReferenceEquals(((NetworkConnection)item).identity.connectionToClient, item) || !ServerSpawned(((NetworkConnection)item).identity)) { return false; } } party = list; return true; } private static bool ActorOwned(NetworkConnectionToClient sender, PlayerInteract actor) { if (sender != null && (Object)(object)actor != (Object)null && ((NetworkBehaviour)actor).isServer && (Object)(object)((NetworkConnection)sender).identity != (Object)null && object.ReferenceEquals(((NetworkBehaviour)actor).netIdentity, ((NetworkConnection)sender).identity) && object.ReferenceEquals(((Component)((NetworkConnection)sender).identity).GetComponent(), actor) && object.ReferenceEquals(((NetworkConnection)sender).identity.connectionToClient, sender)) { return ServerSpawned(((NetworkConnection)sender).identity); } return false; } private static bool HomeReady(GameManager manager, List party) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0019: 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) if (!((Object)(object)manager == (Object)null) && ((NetworkBehaviour)manager).isServer && (int)manager.state == 0) { Scene activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).name != "HomeScene") && !(bool)Transitioning.GetValue(manager) && party != null) { HashSet hashSet = SceneReady.GetValue(manager) as HashSet; 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; } } return false; } private bool LineOfSight(PlayerInteract actor, Vector3 origin) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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_0026: 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_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)vendorCollider == (Object)null) { return false; } Bounds bounds = ((Collider)vendorCollider).bounds; Vector3 center = ((Bounds)(ref bounds)).center; Vector3 val = center - origin; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { return true; } int num = Physics.RaycastNonAlloc(origin, ((Vector3)(ref val)).normalized, hits, ((Vector3)(ref val)).magnitude, LayerMask.op_Implicit(actor.raycastLayer), (QueryTriggerInteraction)1); if (num == hits.Length) { return false; } for (int i = 0; i < num; i++) { if ((Object)(object)((RaycastHit)(ref hits[i])).collider != (Object)null && !((RaycastHit)(ref hits[i])).transform.IsChildOf(((Component)actor).transform) && !((RaycastHit)(ref hits[i])).transform.IsChildOf(vendor.transform)) { return false; } } return true; } private bool FeaturesEnabled() { if (disposed) { return false; } try { return canUse(); } catch (Exception error) { Report("gun shop guard", error); return false; } } internal void PruneDestroyedCasinoItems(ElevatorManager elevator) { //IL_0009: 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) //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Invalid comparison between Unknown and I4 //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: 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) if (disposed) { return; } try { Scene activeScene = SceneManager.GetActiveScene(); object? value = ManagerInstance.GetValue(null); GameManager val = (GameManager)((value is GameManager) ? value : null); object? value2 = ItemInstance.GetValue(null); ItemManager val2 = (ItemManager)((value2 is ItemManager) ? value2 : null); bool enabled = canGuardNativeItems(); bool active = NetworkServer.active; bool game = (Object)(object)val != (Object)null && ((NetworkBehaviour)val).isServer && (int)val.state == 1; bool casino = ((Scene)(ref activeScene)).IsValid() && ((Scene)(ref activeScene)).isLoaded && ((Scene)(ref activeScene)).name == "CasinoScene"; bool currentItems = (Object)(object)val2 != (Object)null && ((NetworkBehaviour)val2).isServer && ServerSpawned(((NetworkBehaviour)val2).netIdentity); int currentElevator; if ((Object)(object)elevator != (Object)null && ((NetworkBehaviour)elevator).isServer && ServerSpawned(((NetworkBehaviour)elevator).netIdentity)) { Scene scene = ((Component)elevator).gameObject.scene; currentElevator = ((((Scene)(ref scene)).handle == ((Scene)(ref activeScene)).handle) ? 1 : 0); } else { currentElevator = 0; } if (GunShopPolicy.CanPruneCasinoInstances(enabled, active, game, casino, currentItems, (byte)currentElevator != 0, (Object)(object)val2 != (Object)null && val2.spawnedItemInstances != null)) { int num = GunShopPolicy.PruneDestroyedInstances(val2.spawnedItemInstances, (ConsumableItem item) => (Object)(object)item != (Object)null); if (num > 0) { Write("Removed " + num + " destroyed Casino item instance record(s) before native elevator transfer; inventory ownership retained."); } } } catch (Exception error) { Report("gun Casino instance cleanup", error); } } private void FindGun() { SpawnableSettings val = Resources.Load("SpawnableSettings"); if ((Object)(object)val == (Object)null || val.spawnables == null) { return; } foreach (SpawnableSO spawnable in val.spawnables) { if ((Object)(object)spawnable != (Object)null && (Object)(object)spawnable.prefab != (Object)null && (Object)(object)spawnable.prefab.GetComponent() != (Object)null) { gun = spawnable; break; } } } private bool GunRegistered() { if ((Object)(object)gun == (Object)null || (Object)(object)gun.prefab == (Object)null) { return false; } NetworkIdentity component = gun.prefab.GetComponent(); if ((Object)(object)component != (Object)null && component.assetId != 0 && component.sceneId == 0 && NetworkClient.prefabs.TryGetValue(component.assetId, out var value)) { return object.ReferenceEquals(value, gun.prefab); } return false; } private void FindAnchor(Scene scene) { //IL_0007: 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) if (IsItemShopAnchor(anchor, scene)) { return; } anchor = null; ClearBusAnchors(); MerchantBuyButton[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (MerchantBuyButton candidate in array) { if (IsItemShopAnchor(candidate, scene)) { anchor = candidate; break; } } } private bool IsItemShopAnchor(MerchantBuyButton candidate, Scene scene) { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0024: 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) if (!((Object)(object)candidate == (Object)null) && ((Component)candidate).gameObject.activeInHierarchy) { Scene scene2 = ((Component)candidate).gameObject.scene; if (!(((Scene)(ref scene2)).handle != ((Scene)(ref scene)).handle) && ServerSpawned(((NetworkBehaviour)candidate).netIdentity)) { anchorHierarchy.Clear(); Transform val = ((Component)candidate).transform; for (int i = 0; i < 16; i++) { if (!((Object)(object)val != (Object)null)) { break; } anchorHierarchy.Add(((Object)val).name); val = val.parent; } return GunShopPolicy.IsItemShopHierarchy(anchorHierarchy); } } return false; } private bool TryPlacement(out Vector3 position, out Quaternion rotation) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0092: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: 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_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: 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_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: 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_011e: Unknown result type (might be due to invalid IL or missing references) //IL_0123: 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_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) //IL_013d: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Unknown result type (might be due to invalid IL or missing references) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_02f0: Unknown result type (might be due to invalid IL or missing references) //IL_02f1: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0303: Unknown result type (might be due to invalid IL or missing references) //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_0231: Unknown result type (might be due to invalid IL or missing references) //IL_0236: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_026d: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_0317: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_0299: Unknown result type (might be due to invalid IL or missing references) //IL_0371: Unknown result type (might be due to invalid IL or missing references) //IL_0382: Unknown result type (might be due to invalid IL or missing references) //IL_0324: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) //IL_03fe: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_0407: Unknown result type (might be due to invalid IL or missing references) //IL_040c: Unknown result type (might be due to invalid IL or missing references) //IL_0411: Unknown result type (might be due to invalid IL or missing references) //IL_0416: Unknown result type (might be due to invalid IL or missing references) //IL_0419: Unknown result type (might be due to invalid IL or missing references) //IL_0428: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_035f: Unknown result type (might be due to invalid IL or missing references) //IL_0364: Unknown result type (might be due to invalid IL or missing references) //IL_0369: Unknown result type (might be due to invalid IL or missing references) //IL_036e: Unknown result type (might be due to invalid IL or missing references) position = Vector3.zero; rotation = Quaternion.identity; if ((Object)(object)anchor == (Object)null || (Object)(object)signRenderer == (Object)null || (Object)(object)busAnchor == (Object)null || busColliders == null || (Object)(object)template == (Object)null) { return false; } BoxCollider component = template.GetComponent(); if ((Object)(object)component == (Object)null || component.size.x <= 0f || component.size.y <= 0f || component.size.z <= 0f) { return false; } Bounds bounds = signRenderer.bounds; Vector3 center = ((Bounds)(ref bounds)).center; Vector3 val = Vector3.ProjectOnPlane(signAnchor.forward, Vector3.up); if (!Finite(center) || !Finite(val) || ((Vector3)(ref val)).sqrMagnitude < 0.01f) { return false; } ((Vector3)(ref val)).Normalize(); if (Vector3.Dot(center - busAnchor.position, val) < 0f) { val = -val; } rotation = Quaternion.LookRotation(val, Vector3.up); Vector3 val2 = rotation * Vector3.right; Vector3 val3 = component.size * 0.5f; val3.x += 0.04f; val3.z += 0.04f; float num = val3.z + 0.08f; bool flag = false; for (int i = 0; i < busColliders.Length; i++) { Collider val4 = busColliders[i]; if ((Object)(object)val4 == (Object)null || !val4.enabled || val4.isTrigger || !((Component)val4).gameObject.activeInHierarchy) { continue; } Bounds bounds2 = val4.bounds; if (!Finite(((Bounds)(ref bounds2)).center) || !Finite(((Bounds)(ref bounds2)).extents)) { return false; } double projectedExtent = GunShopPolicy.ProjectedExtent(val2.x, val2.z, ((Bounds)(ref bounds2)).extents.x, ((Bounds)(ref bounds2)).extents.z); if (GunShopPolicy.OverlapsSignSlice(Vector3.Dot(((Bounds)(ref bounds2)).center - center, val2), projectedExtent, val3.x)) { double num2 = GunShopPolicy.ProjectedExtent(val.x, val.z, ((Bounds)(ref bounds2)).extents.x, ((Bounds)(ref bounds2)).extents.z); if (!GunShopPolicy.Finite(num2)) { return false; } num = Mathf.Max(num, Vector3.Dot(((Bounds)(ref bounds2)).center - center, val) + (float)num2 + val3.z + 0.08f); flag = true; } } if (!flag || !GunShopPolicy.Finite(num) || num > 2f) { return false; } Vector3 val7 = default(Vector3); for (int j = 0; j < 3; j++) { Vector3 val5 = center + val * (num + (float)j * 0.18f); bool flag2 = true; for (int k = 0; k < 5; k++) { Vector3 val6 = val5; if (k > 0) { int num3 = k - 1; val6 += rotation * new Vector3(((num3 & 1) == 0) ? (0f - val3.x) : val3.x, 0f, ((num3 & 2) == 0) ? (0f - val3.z) : val3.z); } if (!TryGround(val6, center.y, center.y - component.size.y - 0.08f, out supportHeights[k], out supportNormals[k])) { flag2 = false; break; } } if (flag2 && GunShopPolicy.TrySupportHeight(supportHeights, supportNormals, out var highest)) { ((Vector3)(ref val7))..ctor(val5.x, (float)highest + 0.03f, val5.z); if (!Physics.CheckBox(val7 + rotation * component.center, val3, rotation, -1, (QueryTriggerInteraction)1)) { position = val7; return true; } } } return false; } private bool CacheBusAnchors() { if ((Object)(object)itemShopRoot != (Object)null && (Object)(object)signAnchor != (Object)null && (Object)(object)busAnchor != (Object)null && (Object)(object)signRenderer != (Object)null && ((Component)signAnchor).gameObject.activeInHierarchy && ((Component)busAnchor).gameObject.activeInHierarchy && signAnchor.IsChildOf(itemShopRoot) && busAnchor.IsChildOf(itemShopRoot) && ((Component)anchor).transform.IsChildOf(itemShopRoot)) { return true; } ClearBusAnchors(); Transform val = ((Component)anchor).transform; int num = 0; while ((Object)(object)val != (Object)null && num < 16) { if (((Object)val).name == "ItemShop" || ((Object)val).name == "ItemShop(Clone)") { itemShopRoot = val; break; } if ((Object)(object)val == (Object)(object)((Component)anchor).transform.root) { break; } num++; val = val.parent; } if ((Object)(object)itemShopRoot == (Object)null) { return false; } Transform val2 = itemShopRoot.Find("Model/Signs/SM_Misfortune"); Transform val3 = itemShopRoot.Find("Model/Base/SM_ItemStoreTruck"); if ((Object)(object)val2 == (Object)null || (Object)(object)val3 == (Object)null || !((Component)val2).gameObject.activeInHierarchy || !((Component)val3).gameObject.activeInHierarchy) { return false; } Renderer component = ((Component)val2).GetComponent(); Collider[] componentsInChildren = ((Component)val3).GetComponentsInChildren(true); if ((Object)(object)component == (Object)null || !component.enabled || componentsInChildren.Length == 0 || componentsInChildren.Length > 64) { return false; } signAnchor = val2; busAnchor = val3; signRenderer = component; busColliders = componentsInChildren; return true; } private bool TryGround(Vector3 point, float signHeight, float maximumGround, out double height, out double normal) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_00bf: 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_0109: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0144: Unknown result type (might be due to invalid IL or missing references) //IL_0166: Unknown result type (might be due to invalid IL or missing references) //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_01b6: Unknown result type (might be due to invalid IL or missing references) height = 0.0; normal = 0.0; int num = Physics.RaycastNonAlloc(new Vector3(point.x, signHeight + 0.5f, point.z), Vector3.down, hits, 8f, -1, (QueryTriggerInteraction)1); if (num == hits.Length) { return false; } bool result = false; float num2 = float.NegativeInfinity; for (int i = 0; i < num; i++) { Collider collider = ((RaycastHit)(ref hits[i])).collider; if (!((Object)(object)collider == (Object)null) && !((Object)(object)collider.attachedRigidbody != (Object)null) && !((Component)collider).transform.IsChildOf(itemShopRoot)) { Scene scene = ((Component)collider).gameObject.scene; if (!(((Scene)(ref scene)).handle != SceneHandle.op_Implicit(observedScene)) && !((Object)(object)((Component)collider).GetComponentInParent() != (Object)null) && !((Object)(object)((Component)collider).GetComponentInParent() != (Object)null) && Finite(((RaycastHit)(ref hits[i])).point) && GunShopPolicy.Finite(((RaycastHit)(ref hits[i])).normal.y) && !((double)((RaycastHit)(ref hits[i])).normal.y < 0.995) && !(((RaycastHit)(ref hits[i])).point.y > maximumGround) && !(((RaycastHit)(ref hits[i])).point.y <= num2)) { num2 = ((RaycastHit)(ref hits[i])).point.y; height = num2; normal = ((RaycastHit)(ref hits[i])).normal.y; result = true; } } } return result; } private static bool Finite(Vector3 value) { if (GunShopPolicy.Finite(value.x) && GunShopPolicy.Finite(value.y)) { return GunShopPolicy.Finite(value.z); } return false; } private void ClearBusAnchors() { itemShopRoot = null; busAnchor = null; signAnchor = null; signRenderer = null; busColliders = null; } private GameObject BuildTemplate() { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000b: Expected O, but got Unknown //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_0114: 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_014e: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01bd: Unknown result type (might be due to invalid IL or missing references) //IL_01c4: Expected O, but got Unknown //IL_01fa: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_0267: Unknown result type (might be due to invalid IL or missing references) //IL_0282: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("Chaos Night Gun Stand Prefab"); val.SetActive(false); Object.DontDestroyOnLoad((Object)(object)val); int num = LayerMask.NameToLayer("Interactable"); if (num >= 0) { val.layer = num; } val.AddComponent(); InteractableEventTrigger val2 = val.AddComponent(); ((InteractableBase)val2).InteractableName = "Gun: 1 ticket (unlimited stock)"; ((InteractableBase)val2).TooltipMessage = "Press [E] to buy a gun for 1 ticket. Collect it free from the casino elevator item guy."; ((InteractableBase)val2).HoldInteract = false; ((InteractableBase)val2).IsInteractable = true; ((InteractableBase)val2).MeetRequirements = true; val.AddComponent(); BoxCollider val3 = val.AddComponent(); val3.center = new Vector3(0f, 0.9f, 0f); val3.size = new Vector3(0.82f, 1.8f, 0.82f); ((Collider)val3).isTrigger = false; Material shared = null; if ((Object)(object)gun != (Object)null && (Object)(object)gun.prefab != (Object)null) { Renderer componentInChildren = gun.prefab.GetComponentInChildren(true); if ((Object)(object)componentInChildren != (Object)null) { shared = componentInChildren.sharedMaterial; } } Cube(val.transform, "Stand", new Vector3(0f, 0.45f, 0f), new Vector3(0.72f, 0.9f, 0.72f), shared); Cube(val.transform, "Shelf", new Vector3(0f, 0.93f, 0f), new Vector3(0.85f, 0.08f, 0.85f), shared); Cube(val.transform, "Sign", new Vector3(0f, 1.42f, -0.29f), new Vector3(0.85f, 0.72f, 0.06f), shared); if ((Object)(object)TMP_Settings.defaultFontAsset != (Object)null) { GameObject val4 = new GameObject("Price"); val4.transform.SetParent(val.transform, false); val4.layer = val.layer; val4.transform.localPosition = new Vector3(0f, 1.42f, -0.25f); val4.transform.localRotation = Quaternion.Euler(0f, 180f, 0f); TextMeshPro val5 = val4.AddComponent(); ((TMP_Text)val5).font = TMP_Settings.defaultFontAsset; ((TMP_Text)val5).text = "GUN\n1 TICKET\nUNLIMITED STOCK"; ((TMP_Text)val5).richText = false; ((TMP_Text)val5).fontSize = 3.3f; ((TMP_Text)val5).alignment = (TextAlignmentOptions)514; ((Graphic)val5).color = Color.white; ((TMP_Text)val5).rectTransform.sizeDelta = new Vector2(0.8f, 0.65f); } AddGunPreview(val.transform); return val; } private static void Cube(Transform parent, string name, Vector3 position, Vector3 size, Material shared) { //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)val).name = name; val.transform.SetParent(parent, false); val.layer = ((Component)parent).gameObject.layer; val.transform.localPosition = position; val.transform.localScale = size; Collider component = val.GetComponent(); if ((Object)(object)component != (Object)null) { Object.DestroyImmediate((Object)(object)component); } if ((Object)(object)shared != (Object)null) { val.GetComponent().sharedMaterial = shared; } } private void AddGunPreview(Transform parent) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01e1: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Expected O, but got Unknown //IL_0113: 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_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014f: Unknown result type (might be due to invalid IL or missing references) //IL_0167: Unknown result type (might be due to invalid IL or missing references) //IL_01ae: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)gun == (Object)null || (Object)(object)gun.prefab == (Object)null) { return; } GameObject val = new GameObject("Native gun display"); val.transform.SetParent(parent, false); val.transform.localPosition = new Vector3(0f, 1.08f, 0f); MeshFilter[] componentsInChildren = gun.prefab.GetComponentsInChildren(true); Bounds bounds = default(Bounds); ((Bounds)(ref bounds))..ctor(Vector3.zero, Vector3.zero); bool flag = false; int num = 0; MeshFilter[] array = componentsInChildren; foreach (MeshFilter val2 in array) { MeshRenderer component = ((Component)val2).GetComponent(); if (!((Object)(object)val2.sharedMesh == (Object)null) && !((Object)(object)component == (Object)null) && num < 12) { GameObject val3 = new GameObject("Gun mesh"); val3.transform.SetParent(val.transform, false); val3.layer = ((Component)parent).gameObject.layer; val3.transform.localPosition = gun.prefab.transform.InverseTransformPoint(((Component)val2).transform.position); val3.transform.localRotation = Quaternion.Inverse(gun.prefab.transform.rotation) * ((Component)val2).transform.rotation; val3.transform.localScale = ((Component)val2).transform.lossyScale; val3.AddComponent().sharedMesh = val2.sharedMesh; MeshRenderer val4 = val3.AddComponent(); ((Renderer)val4).sharedMaterials = ((Renderer)component).sharedMaterials; if (!flag) { bounds = ((Renderer)val4).bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(((Renderer)val4).bounds); } num++; } } if (flag) { float num2 = Mathf.Max(((Bounds)(ref bounds)).size.x, Mathf.Max(((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds)).size.z)); if (num2 > 0.001f) { val.transform.localScale = Vector3.one * Mathf.Min(1f, 0.62f / num2); } } } private static bool ServerSpawned(NetworkIdentity identity) { if ((Object)(object)identity != (Object)null && identity.netId != 0 && identity.isServer && NetworkServer.spawned.TryGetValue(identity.netId, out var value)) { return object.ReferenceEquals(value, identity); } return false; } private static bool HasHandler(uint id) { IDictionary dictionary = SpawnHandlers.GetValue(null) as IDictionary; IDictionary dictionary2 = UnspawnHandlers.GetValue(null) as IDictionary; if (dictionary != null && dictionary2 != null && !dictionary.Contains(id)) { return dictionary2.Contains(id); } return true; } private void PruneAttempts(List party) { List list = null; foreach (KeyValuePair attempt in attempts) { if (!party.Contains(attempt.Value.Connection)) { if (list == null) { list = new List(); } list.Add(attempt.Key); } } if (list == null) { return; } foreach (int item in list) { attempts.Remove(item); } } private static void DestroySpawn(GameObject instance) { if (!((Object)(object)instance == (Object)null)) { NetworkIdentity component = instance.GetComponent(); if (NetworkServer.active && ServerSpawned(component)) { NetworkServer.Destroy(instance); } else { Object.Destroy((Object)(object)instance); } } } private void DestroyVendor() { if ((Object)(object)vendor != (Object)null) { DestroySpawn(vendor); } vendor = null; vendorInteract = null; vendorCollider = null; } private void Write(string message) { if (log != null) { try { log(message); } catch { } } } private void Hold(string message) { if (lastHold != message) { lastHold = message; Write(message); } } 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 && ItemInstance != null && ItemInstance.IsStatic && MoneyInstance != null && MoneyInstance.IsStatic && Transitioning != null && SceneReady != null && ExpectedPlayers != null && InteractLocked != null && SpawnHandlers != null && UnspawnHandlers != null && AccessTools.Method(typeof(InteractableBase), "InvokeUserCode_CmdOnInteract__PlayerInteract", (Type[])null, (Type[])null) != null) { return AccessTools.Method(typeof(ElevatorManager), "ServerElevatorRoutine", new Type[1] { typeof(int) }, (Type[])null) != null; } return false; } public static string SelfTest() { if (!NativeApiAvailable()) { throw new InvalidOperationException("Native gun shop API shape changed."); } if (!GunShopPolicy.CanBuy(enabled: true, server: true, homeReady: true, senderOwned: true, unlocked: true, 1L, 1.0, 1f, 0f) || GunShopPolicy.CanBuy(enabled: true, server: true, homeReady: true, senderOwned: false, unlocked: true, 1L, 1.0, 1f, 0f) || GunShopPolicy.CanBuy(enabled: true, server: true, homeReady: true, senderOwned: true, unlocked: true, 0L, 1.0, 1f, 0f) || GunShopPolicy.CanBuy(enabled: true, server: true, homeReady: true, senderOwned: true, unlocked: true, 1L, 1.0, 1f, 2f) || !GunShopPolicy.ExactCharge(2L, 1L) || GunShopPolicy.ExactCharge(2L, 0L)) { throw new InvalidOperationException("Native gun shop policy failed."); } return "PASS: native gun shop API, fixed ticket price and authority/range/rate policy (offline)"; } public void Dispose() { if (disposed) { return; } DestroyVendor(); attempts.Clear(); disposed = true; if (object.ReferenceEquals(Active, this)) { Active = null; } if ((Object)(object)template != (Object)null && NetworkClient.prefabs.TryGetValue(1129203539u, out var value) && object.ReferenceEquals(value, template)) { if (!HasHandler(1129203539u)) { NetworkClient.UnregisterPrefab(template); } else { NetworkClient.prefabs.Remove(1129203539u); } } if ((Object)(object)template != (Object)null) { Object.Destroy((Object)(object)template); } template = null; gun = null; anchor = null; ClearBusAnchors(); } } internal sealed class GunShopCommandContext { [ThreadStatic] internal static GunShopCommandContext Current; internal NetworkBehaviour Target; internal NetworkConnectionToClient Sender; } [HarmonyPatch(typeof(InteractableBase), "InvokeUserCode_CmdOnInteract__PlayerInteract")] internal static class GunShopNativeSenderPatch { private static void Prefix(NetworkBehaviour __0, NetworkConnectionToClient __2, out GunShopCommandContext __state) { __state = GunShopCommandContext.Current; ChaosGunShop active = ChaosGunShop.Active; object current; if (active == null || !active.IsOwnVendor(__0)) { current = null; } else { GunShopCommandContext gunShopCommandContext = new GunShopCommandContext(); gunShopCommandContext.Target = __0; gunShopCommandContext.Sender = __2; current = gunShopCommandContext; } GunShopCommandContext.Current = (GunShopCommandContext)current; } private static Exception Finalizer(Exception __exception, GunShopCommandContext __state) { GunShopCommandContext.Current = __state; return __exception; } } [HarmonyPatch(typeof(InteractableEventTrigger), "ServerOnInteract")] internal static class GunShopPurchasePatch { private static bool Prefix(InteractableEventTrigger __instance, PlayerInteract __0) { ChaosGunShop active = ChaosGunShop.Active; if (active == null || !active.IsOwnVendor((NetworkBehaviour)(object)__instance)) { return true; } active.Purchase(__instance, __0); return false; } } [HarmonyPatch(typeof(CustomNetworkManager), "OnClientConnect")] internal static class GunShopClientRegistrationPatch { private static void Prefix() { if (ChaosGunShop.Active != null) { ChaosGunShop.Active.EnsureRegistered(); } } } [HarmonyPatch(typeof(ElevatorManager), "ServerElevatorRoutine")] internal static class GunShopCasinoItemCleanupPatch { private static void Prefix(ElevatorManager __instance) { if (ChaosGunShop.Active != null) { ChaosGunShop.Active.PruneDestroyedCasinoItems(__instance); } } } public static class ChaosGunTargeting { [HarmonyPatch(typeof(QuotaGun), "Shoot")] private static class NativeCameraShotPatch { private static IEnumerable Transpiler(IEnumerable original) { //IL_0203: Unknown result type (might be due to invalid IL or missing references) //IL_020a: Expected O, but got Unknown List list = new List(original); int num = 0; int num2 = 0; int num3 = 0; int num4 = 0; foreach (CodeInstruction item in list) { MethodInfo methodInfo = item.operand as MethodInfo; if (!(methodInfo == null)) { if (methodInfo.DeclaringType == typeof(Transform) && methodInfo.Name == "get_position" && num++ == 0) { item.opcode = OpCodes.Call; item.operand = AccessTools.Method(typeof(ChaosGunTargeting), "CameraRayOrigin", (Type[])null, (Type[])null); } else if (methodInfo.DeclaringType == typeof(Transform) && methodInfo.Name == "get_forward" && num2++ == 0) { item.opcode = OpCodes.Call; item.operand = AccessTools.Method(typeof(ChaosGunTargeting), "CameraRayDirection", (Type[])null, (Type[])null); } else if (methodInfo.DeclaringType == typeof(Collider) && methodInfo.Name == "get_attachedRigidbody") { item.opcode = OpCodes.Call; item.operand = AccessTools.Method(typeof(ChaosGunTargeting), "NativeHitBody", (Type[])null, (Type[])null); num3++; } } } for (int i = 0; i < list.Count; i++) { MethodInfo methodInfo2 = list[i].operand as MethodInfo; if (!(methodInfo2 == null) && !(methodInfo2.DeclaringType != typeof(Physics)) && !(methodInfo2.Name != "RaycastNonAlloc") && methodInfo2.GetParameters().Length == 5 && !(methodInfo2.GetParameters()[0].ParameterType != typeof(Ray))) { CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[i].labels); list[i].labels.Clear(); val.blocks.AddRange(list[i].blocks); list[i].blocks.Clear(); list.Insert(i, val); i++; list[i].opcode = OpCodes.Call; list[i].operand = AccessTools.Method(typeof(ChaosGunTargeting), "ClientRaycast", (Type[])null, (Type[])null); num4++; } } if (num != 1 || num2 != 1 || num3 != 1 || num4 != 1) { throw new InvalidOperationException("Native QuotaGun camera/hit layout changed; refusing partial aiming patch."); } return list; } } private const int MaximumHits = 64; private static readonly RaycastHit[] clientHits = (RaycastHit[])(object)new RaycastHit[64]; private static readonly RaycastHit[] serverHits = (RaycastHit[])(object)new RaycastHit[64]; private static readonly FieldInfo GunRange = AccessTools.Field(typeof(QuotaGun), "raycastDistance"); private static readonly FieldInfo GunMask = AccessTools.Field(typeof(QuotaGun), "rayMask"); public static event Action NpcPanicTakingControl; public static bool NpcPanicControls(NPC npc) { return ChaosNpcCombat.PanicControlsNpc(npc); } public static bool NpcConfirmedDead(NPC npc) { return ChaosNpcCombat.ConfirmedDead(npc); } internal static void TakePanicControl(NPC npc) { ChaosGunTargeting.NpcPanicTakingControl?.Invoke(npc); } public static bool TryResolveNpc(Collider collider, out NPC npc) { npc = null; Transform val = (((Object)(object)collider == (Object)null) ? null : ((Component)collider).transform); int num = 0; while ((Object)(object)val != (Object)null && num < 32) { NPC component = ((Component)val).GetComponent(); if ((Object)(object)component != (Object)null) { npc = component; return true; } if ((Object)(object)((Component)val).GetComponent() != (Object)null || (Object)(object)((Component)val).GetComponent() != (Object)null || (Object)(object)((Component)val).GetComponent() != (Object)null) { return false; } val = val.parent; num++; } return false; } public static Rigidbody NativeHitBody(Collider collider) { if (!TryResolveNpc(collider, out var npc)) { if (!((Object)(object)collider == (Object)null)) { return collider.attachedRigidbody; } return null; } return ((Component)npc).GetComponent(); } public static Vector3 CameraRayOrigin(Transform cameraTransform) { //IL_002c: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) Camera val = (((Object)(object)cameraTransform == (Object)null) ? null : ((Component)cameraTransform).GetComponent()); if (!((Object)(object)val == (Object)null)) { Ray val2 = val.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f)); return ((Ray)(ref val2)).origin; } if (!((Object)(object)cameraTransform == (Object)null)) { return cameraTransform.position; } return Vector3.zero; } public static Vector3 CameraRayDirection(Transform cameraTransform) { //IL_002c: 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_0036: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0049: Unknown result type (might be due to invalid IL or missing references) Camera val = (((Object)(object)cameraTransform == (Object)null) ? null : ((Component)cameraTransform).GetComponent()); if (!((Object)(object)val == (Object)null)) { Ray val2 = val.ViewportPointToRay(new Vector3(0.5f, 0.5f, 0f)); return ((Ray)(ref val2)).direction; } if (!((Object)(object)cameraTransform == (Object)null)) { return cameraTransform.forward; } return Vector3.forward; } public static int ClientRaycast(Ray ray, RaycastHit[] results, float range, int mask, QueryTriggerInteraction triggers, QuotaGun gun) { //IL_0014: 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) //IL_004a: 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_007a: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) if (results == null || results.Length == 0 || (Object)(object)gun == (Object)null || !Finite(((Ray)(ref ray)).origin) || !Finite(((Ray)(ref ray)).direction) || !Finite(range) || range <= 0f || range > 250f) { return 0; } if (!FirstHit(((Ray)(ref ray)).origin, ((Ray)(ref ray)).direction, range, mask, ((Item)gun).NetworkHolder, gun, clientHits, out var closest, out var _)) { return 0; } results[0] = closest; return 1; } public static bool TryValidateNpcShot(QuotaGun gun, NetworkConnectionToClient connection, NPC npc, Vector3 direction, Vector3 reportedPoint, out RaycastHit hit) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_01ad: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01cc: Unknown result type (might be due to invalid IL or missing references) //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0256: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) //IL_026f: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Unknown result type (might be due to invalid IL or missing references) //IL_02b7: Unknown result type (might be due to invalid IL or missing references) //IL_02bb: Unknown result type (might be due to invalid IL or missing references) //IL_0288: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_02fb: Unknown result type (might be due to invalid IL or missing references) //IL_0300: Unknown result type (might be due to invalid IL or missing references) hit = default(RaycastHit); if (!NetworkServer.active || (Object)(object)gun == (Object)null || (Object)(object)npc == (Object)null || connection == null || !((NetworkBehaviour)gun).isServer || !((NetworkBehaviour)npc).isServer || !((NetworkConnection)connection).isAuthenticated || !((NetworkConnection)connection).isReady || (Object)(object)((NetworkConnection)connection).identity == (Object)null || GunRange == null || GunMask == null) { return false; } NetworkIdentity identity = ((NetworkConnection)connection).identity; if (!NetworkServer.connections.TryGetValue(connection.connectionId, out var value) || !object.ReferenceEquals(value, connection) || identity.netId == 0 || !object.ReferenceEquals(identity.connectionToClient, connection) || !NetworkServer.spawned.TryGetValue(identity.netId, out var value2) || !object.ReferenceEquals(value2, identity)) { return false; } PlayerProfile component = ((Component)identity).GetComponent(); PlayerController component2 = ((Component)identity).GetComponent(); PlayerInventory networkHolder = ((Item)gun).NetworkHolder; if ((Object)(object)component == (Object)null || component.steamId == 0 || (Object)(object)component2 == (Object)null || (Object)(object)component2.head == (Object)null || (Object)(object)networkHolder == (Object)null || !object.ReferenceEquals(((NetworkBehaviour)networkHolder).netIdentity, identity) || (Object)(object)networkHolder.holdingItem != (Object)(object)gun || ((Item)gun).isInPocket || ((NetworkBehaviour)gun).netId == 0 || !NetworkServer.spawned.TryGetValue(((NetworkBehaviour)gun).netId, out value2) || !object.ReferenceEquals(value2, ((NetworkBehaviour)gun).netIdentity) || ((NetworkBehaviour)npc).netId == 0 || !NetworkServer.spawned.TryGetValue(((NetworkBehaviour)npc).netId, out value2) || !object.ReferenceEquals(value2, ((NetworkBehaviour)npc).netIdentity) || !((Component)npc).gameObject.activeInHierarchy || !Finite(direction) || !Finite(reportedPoint)) { return false; } Vector3 position = ((Component)component2.head).transform.position; float num = (float)GunRange.GetValue(gun); if (!NpcCombatPolicy.TryCameraLine(position.x, position.y, position.z, reportedPoint.x, reportedPoint.y, reportedPoint.z, direction.x, direction.y, direction.z, num, out var offsetX, out var offsetY, out var offsetZ, out var _)) { return false; } Vector3 val = position + new Vector3((float)offsetX, (float)offsetY, (float)offsetZ); int mask = LayerMask.op_Implicit((LayerMask)GunMask.GetValue(gun)); Vector3 val2 = val - position; if (((Vector3)(ref val2)).sqrMagnitude > 0.0001f && (FirstHit(position, ((Vector3)(ref val2)).normalized, ((Vector3)(ref val2)).magnitude, mask, networkHolder, gun, serverHits, out var _, out var saturated) || saturated)) { return false; } if (!FirstHit(val, ((Vector3)(ref direction)).normalized, num + 0.4f, mask, networkHolder, gun, serverHits, out hit, out saturated)) { return false; } if (TryResolveNpc(((RaycastHit)(ref hit)).collider, out var npc2) && object.ReferenceEquals(npc2, npc)) { return (double)Vector3.Distance(((RaycastHit)(ref hit)).point, reportedPoint) <= 1.0; } return false; } private static bool FirstHit(Vector3 origin, Vector3 direction, float range, int mask, PlayerInventory owner, QuotaGun gun, RaycastHit[] buffer, out RaycastHit closest, out bool saturated) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //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_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_00cc: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) closest = default(RaycastHit); int num = Physics.RaycastNonAlloc(origin, direction, buffer, range, mask, (QueryTriggerInteraction)1); saturated = num >= buffer.Length; if (saturated) { return false; } float num2 = float.PositiveInfinity; bool closestNpc = false; for (int i = 0; i < num; i++) { RaycastHit val = buffer[i]; Collider collider = ((RaycastHit)(ref val)).collider; bool ownedCollider = (Object)(object)collider != (Object)null && (((Object)(object)owner != (Object)null && ((Component)collider).transform.IsChildOf(((Component)owner).transform)) || ((Object)(object)gun != (Object)null && ((Component)collider).transform.IsChildOf(((Component)gun).transform))); NPC npc; bool flag = TryResolveNpc(collider, out npc); if (NpcCombatPolicy.PreferObstruction((Object)(object)collider != (Object)null, ownedCollider, ((RaycastHit)(ref val)).distance, num2, flag, closestNpc)) { closest = val; num2 = ((RaycastHit)(ref val)).distance; closestNpc = flag; } } return !float.IsPositiveInfinity(num2); } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private static bool Finite(Vector3 value) { if (Finite(value.x) && Finite(value.y)) { return Finite(value.z); } return false; } } internal static class ChaosJson { private static readonly JsonSerializerSettings Settings; public static string Write(object value) { return JsonConvert.SerializeObject(value, (Formatting)0, Settings); } public static T Read(string json) { if (string.IsNullOrEmpty(json)) { throw new ArgumentException("Empty Chaos JSON."); } return JsonConvert.DeserializeObject(json, Settings); } static ChaosJson() { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown JsonSerializerSettings val = new JsonSerializerSettings(); val.TypeNameHandling = (TypeNameHandling)0; val.MaxDepth = 64; val.CheckAdditionalContent = true; val.MissingMemberHandling = (MissingMemberHandling)0; Settings = val; } } [BepInPlugin("co.oasistechnologies.gwyf.chaosnight", "Chaos Night", "0.6.4")] [BepInProcess("Gamble With Your Friends.exe")] public sealed class Plugin : BaseUnityPlugin { private sealed class PaidNotice { public string Task; public string Actor; public BigNumber Cash; public bool Penalty; } private sealed class WagerStamp { public int Game; public int Mask; public int Featured; public int SourceId; public BigNumber Bet; } public const string Guid = "co.oasistechnologies.gwyf.chaosnight"; public const string Version = "0.6.4"; public const string SupportedAssemblyHash = "f2147868930f1fc9df3489ef89339759f7eeb8a92c270bec094a4ece6dd2935b"; private ChaosRoundTravel roundTravel; private ChaosNpcCombat npcCombat; private ChaosGunShop gunShop; private ChaosGunReload gunReload; private ChaosBodyRepair bodyRepair; private ChaosBedWake bedWake; private ChaosWardrobe wardrobe; private ChaosNeckwear neckwear; private ChaosDayEndOverlay dayEndOverlay; private ChaosBetDisplay betDisplay; private ChaosAllIn allIn; private string bodyRepairNote; private bool bodyRepairAvailability; private string bodyRepairAvailabilityHint; private bool nativeFeatureFaulted; private UpgradeUI nativeUpgradePresentation; private static readonly FieldInfo NativeChallengeParent = AccessTools.Field(typeof(ChallengeManager), "activeChallengeListParent"); private static readonly FieldInfo NativeUpgradeInstance = AccessTools.Field(typeof(UpgradeUI), "_instance"); private static readonly FieldInfo NativeUpgradeParent = AccessTools.Field(typeof(UpgradeUI), "entryParent"); private PoliceBailCoordinator policeBail = new PoliceBailCoordinator(); private PoliceBailPlan policeBailPlan; private PoliceBailJournalState policeBailState; private PoliceBailJournal policeBailJournal; private ChaosPoliceBailHud policeBailHud; private string policeBailGeneration = System.Guid.NewGuid().ToString("N"); private bool policeBailResume; private bool policeBailInputSubscribed; private bool policeBailELatched; private bool policeBailKeyReleased; private bool policeBailLastBlocked; private float policeBailNextAck; private int policeBailClosedFrame = -1; private StatsLedger statsLifetime; private StatsLedger statsRun; private StatsStore statsLifetimeStore; private StatsStore statsRunStore; private StatsStore statsIndexStore; private string statsIndexPath; private bool statsIndexDirty; private string statsLifetimePath; private string statsRunPath; private string statsSaveKey; private string statsError; private long statsPaidSequence; private long statsLifetimeSaved = -1L; private long statsRunSaved = -1L; private bool statsForceFresh; private float statsNextSave; private readonly StatsWagers statsWagers = new StatsWagers(); private readonly StatsWriter statsWriter = new StatsWriter(); private readonly Dictionary statsOpenRuns = new Dictionary(StringComparer.Ordinal); internal static GameBase StatsResultContext; private ChaosStatsArt statsArt; private float lastNegativeTime; private float nextNegativePresentation; private static readonly FieldInfo NativeTransition = AccessTools.Field(typeof(GameManager), "_isTransitioning"); private readonly Dictionary paidNotices = new Dictionary(); internal static Plugin Instance; private ConfigEntry chaosEnabled; private ConfigEntry closing; private ConfigEntry contracts; private ConfigEntry reducedMotion; private ConfigEntry negatives; private ConfigEntry timedNegatives; private ConfigEntry showHud; private ConfigEntry intensity; private ConfigEntry seed; private ConfigEntry penaltySeverity; private ConfigEntry jackpot; private ConfigEntry bonusCap; private ConfigEntry riskDayChance; private ConfigEntry excluded; private Engine engine; private ChaosNetwork network; private ChaosUi ui; private ChaosSave saves; private Harmony harmony; private UiState guestView; private string session = System.Guid.NewGuid().ToString("N"); private string npcAttemptEpoch = System.Guid.NewGuid().ToString("N"); private string guestSession; private string guestNpcDayToken; private string lastSave; private string holdReason; private string lastNetworkStatus; private long revision; private long guestRevision = -1L; private bool compatible; private bool inDay; private bool quotaChanged; private bool uiWasOpen; private bool checkpointLoaded; private bool daySuspended; private bool wasHost; private int skippedNativeDay = -1; private InputLayer previousLayer; private BigNumber originalQuota; private BigNumber adjustedQuota; private BigNumber nativeChaosCashDay; private float originalDuration; private long dayStartTickets = 1L; private long stagedShopSpend; private long preDayShopSpend; private BigNumber nativeStartBalance; private long nativeStartTickets; private bool nativeBaselineLoaded; private int merchantDepth; private float nextStatusCheck; private float nextBroadcast; private bool broadcastPending; private bool viewDirty; private float nextView; private string preDayEngineJson; private long eventTicks; private long eventCount; private bool firstUpdate; private readonly Dictionary> wagers = new Dictionary>(); internal static GameBase BetContext; private readonly HashSet livePlayers = new HashSet(); private readonly Dictionary rewardPlayers = new Dictionary(); private static readonly MethodInfo SetQuotaMethod = AccessTools.PropertySetter(typeof(GameManager), "currentQuota"); private static readonly MethodInfo GetDurationMethod = AccessTools.PropertyGetter(typeof(GameManager), "DayDuration"); private static readonly FieldInfo SaveDataField = AccessTools.Field(typeof(SaveManager), "currentSaveData"); private static readonly MethodInfo GetSaveDirectoryMethod = AccessTools.PropertyGetter(typeof(SaveManager), "SaveDirectoryPath"); private VoteCoordinator restartVote; private string dayStartNativeJson; private string sessionControlNote; private int dayStartNativeDay; private bool sessionResetActive; private SaveData pendingHomeRestore; private int previousHomeSceneHandle; private NetworkConnectionToClient resetHostConnection; private float resetStartedAt; private float nextResetDiagnostic; private GameBase[] resetWaitGames; private string lastRecoveryPath; private bool restartCompleted; private static readonly FieldInfo NativeManagerInstanceField = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly FieldInfo NativeTransitionField = AccessTools.Field(typeof(GameManager), "_isTransitioning"); private static readonly FieldInfo NativeLoadedField = AccessTools.Field(typeof(SaveManager), "hasLoadedGame"); private static readonly FieldInfo SceneReadyField = AccessTools.Field(typeof(GameManager), "_scenePlayReady"); private static readonly FieldInfo SceneExpectedField = AccessTools.Field(typeof(GameManager), "_expectedScenePlayers"); internal bool TimerReturnAllowed { get { if (compatible && !sessionResetActive && !NativeTransitioning && !BlockNewWagers && network != null && network.ReadyForChaos) { return ReadyNativeParty() != null; } return false; } } private bool PoliceBailBlocking { get { if (policeBail != null) { return policeBail.Active; } return false; } } internal bool PoliceBailHoldsProgress { get { if (NetworkServer.active) { return PoliceBailBlocking; } return false; } } public bool PoliceBailBlocksInput { get { if (Time.frameCount != policeBailClosedFrame) { return PoliceBailReceiptVisible; } return true; } } private bool PoliceBailReceiptVisible { get { if (!NetworkServer.active) { if (guestView != null) { return guestView.PoliceBailVisible; } return false; } if (policeBail == null) { return false; } PoliceBailPhase phase = policeBail.Snapshot().Phase; if (phase != PoliceBailPhase.AwaitingHome && phase != PoliceBailPhase.WaitingForContinue) { return phase == PoliceBailPhase.Held; } return true; } } private BigNumber PoliceOriginalCash { get { //IL_0024: 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) if (policeBailState != null) { return new BigNumber(policeBailState.OriginalCashMantissa, policeBailState.OriginalCashExponent); } return BigNumber.Zero; } } private string StatsDirectory => Path.Combine(Paths.ConfigPath, "ChaosNight", "Stats"); private bool HasPendingWagers { get { foreach (Queue value in wagers.Values) { if (value.Count > 0) { return true; } } return false; } } internal bool BlockEscape { get { if (!PoliceBailBlocksInput) { if (ui != null) { return ui.WantsWorldInputBlocked; } return false; } return true; } } private bool NativeTransitioning { get { GameManager val = (GameManager)((NativeManagerInstanceField == null) ? null : /*isinst with value type is only supported in some contexts*/); if (!((Object)(object)val == (Object)null) && !(NativeTransitionField == null)) { return (bool)NativeTransitionField.GetValue(val); } return true; } } internal bool BlockNewWagers { get { if (!sessionResetActive && !PoliceBailBlocking) { if (restartVote != null && restartVote.State.Status == VoteStatus.Passed) { return !restartVote.State.Consumed; } return false; } return true; } } internal void PrepareExpiredTimerItems(ElevatorManager elevator) { if (compatible && !sessionResetActive && !NativeTransitioning && gunShop != null) { gunShop.PruneDestroyedCasinoItems(elevator); } } private bool NativeFeaturesAllowed() { if (!nativeFeatureFaulted && compatible && network != null && network.ReadyForChaos && !sessionResetActive && !BlockNewWagers && string.IsNullOrEmpty(holdReason)) { return !NativeTransitioning; } return false; } private bool LocalHomeFeaturesAllowed() { if (compatible && !sessionResetActive && !NativeTransitioning) { return !PoliceBailBlocksInput; } return false; } private List ReadyNativeParty() { if (!NetworkServer.active || ConnectedVoters() == null) { return null; } List list = new List(); foreach (NetworkConnectionToClient value in NetworkServer.connections.Values) { if (!SessionIdentity.TryResolve(SessionPlayerProof(value), out var _)) { return null; } list.Add(value); } list.Sort(delegate(NetworkConnectionToClient a, NetworkConnectionToClient b) { int connectionId = a.connectionId; return connectionId.CompareTo(b.connectionId); }); if (list.Count != 0) { return list; } return null; } private void InitializeNativeFeatures() { Action action = delegate(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); }; betDisplay = new ChaosBetDisplay(() => compatible && !nativeFeatureFaulted && !sessionResetActive, action); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Native bet display self-test: " + ChaosBetDisplay.SelfTest())); allIn = new ChaosAllIn(AllInModifierEnabled, AllInPermissionAvailable, ConsumeAllInPermission, AuthenticAllInPlayer, action); ((BaseUnityPlugin)this).Logger.LogInfo((object)("BET IT ALL native self-test: " + ChaosAllIn.SelfTest())); roundTravel = new ChaosRoundTravel(NativeFeaturesAllowed, ReadyNativeParty, () => !HasPendingWagers && !AnyCasinoGameRunning(), action); npcCombat = new ChaosNpcCombat(NativeFeaturesAllowed, AwardNpcKill, action, () => compatible, Changed); long shopSpendBeforeGunCharge = 0L; gunShop = new ChaosGunShop(NativeFeaturesAllowed, ReadyNativeParty, delegate(bool entered) { if (entered) { shopSpendBeforeGunCharge = stagedShopSpend; EnterMerchant(); } else { LeaveMerchant(); } }, action, delegate { stagedShopSpend = shopSpendBeforeGunCharge; }, () => compatible && !sessionResetActive && !NativeTransitioning, ConfirmGunPurchase); gunReload = new ChaosGunReload(network, () => compatible && !sessionResetActive && !PoliceBailBlocking && !NativeTransitioning && network.ReadyForChaos, () => reducedMotion.Value, () => ui != null && ui.WantsWorldInputBlocked, delegate { SaveData saveData = GetSaveData(); return (saveData != null) ? (saveData.saveName + "/" + saveData.seed) : null; }, Paths.ConfigPath, action); bodyRepair = new ChaosBodyRepair(NativeFeaturesAllowed, ReadyNativeParty, action); bedWake = new ChaosBedWake(LocalHomeFeaturesAllowed, action); wardrobe = new ChaosWardrobe(LocalHomeFeaturesAllowed, action); neckwear = new ChaosNeckwear(() => compatible, action); dayEndOverlay = new ChaosDayEndOverlay(() => compatible && !sessionResetActive && !NativeTransitioning, action); BindCachedNativeUpgradePresentation(); } private bool AllInModifierEnabled() { if (!NativeFeaturesAllowed()) { return false; } if (NetworkServer.active) { if (CurrentNpcDayActive() && engine != null && engine.State.DayEnabled) { return engine.State.ModifierIds.Contains("bet_it_all"); } return false; } if (guestView != null && guestView.GameplayActive) { return guestView.AllInEnabled; } return false; } private bool AllInPermissionAvailable() { if (!NativeFeaturesAllowed()) { return false; } if (NetworkServer.active) { if (CurrentNpcDayActive() && engine != null) { return engine.CanBetItAll(); } return false; } if (guestView != null && guestView.GameplayActive) { return guestView.AllInAvailable; } return false; } private bool ConsumeAllInPermission() { if (!NetworkServer.active || !AllInPermissionAvailable() || !engine.TryConsumeBetItAll()) { return false; } Changed(); return true; } private bool AuthenticAllInPlayer(PlayerProfile player) { if (NetworkServer.active && (Object)(object)player != (Object)null && ((NetworkBehaviour)player).isServer && SessionIdentity.TryResolve(SessionPlayerProof(((NetworkBehaviour)player).connectionToClient), out var steamId) && steamId == player.steamId && ((NetworkBehaviour)player).connectionToClient != null && (Object)(object)((NetworkConnection)((NetworkBehaviour)player).connectionToClient).identity != (Object)null) { return object.ReferenceEquals(((Component)((NetworkConnection)((NetworkBehaviour)player).connectionToClient).identity).GetComponent(), player); } return false; } private void ConfirmGunPurchase(PlayerProfile buyer) { if (NetworkServer.active && network != null && !((Object)(object)buyer == (Object)null) && ((NetworkBehaviour)buyer).isServer) { string text = DisplayText(buyer.playerName, 96); if (string.IsNullOrWhiteSpace(text)) { text = "A player"; } network.BroadcastCompletion(new CompletionNotice { Task = "Gun ready for pickup", Actor = text, Amount = "1 ticket", Penalty = false }); } } private void AddBodyRepairControls(UiState view) { string note = "Start a compatible game to use player repairs."; view.CanRepairAllBodies = bodyRepair != null && bodyRepair.CanRepair(out note); bodyRepairAvailability = view.CanRepairAllBodies; bodyRepairAvailabilityHint = note; view.BodyRepairHint = (string.IsNullOrEmpty(bodyRepairNote) ? note : (bodyRepairNote + "\n" + note)); } private void RepairAllPlayerBodies() { if (NetworkServer.active && bodyRepair != null) { bodyRepair.TryRepair(out var note); bodyRepairNote = note; Changed(); } } private string CurrentNpcDayToken() { if (!NetworkServer.active) { return guestNpcDayToken ?? ""; } if (engine != null && !string.IsNullOrEmpty(engine.State.RunId)) { return session + "/" + engine.State.RunId + "/" + engine.State.Round + "/" + npcAttemptEpoch; } return ""; } private bool CurrentNpcDayActive() { if (!NetworkServer.active) { if (NetworkClient.active && guestView != null && guestView.GameplayActive && network != null && network.ReadyForChaos) { return !sessionResetActive; } return false; } if (inDay && !daySuspended && engine != null && engine.State.DayOpen && engine.State.DayEnabled && !sessionResetActive) { return string.IsNullOrEmpty(holdReason); } return false; } private void SyncLocalNpcDeaths() { if (npcCombat != null && NetworkServer.active && engine != null) { npcCombat.SetAuthoritativeDeaths(CurrentNpcDayToken(), engine.State.KilledNpcKeys.ToArray(), CurrentNpcDayActive()); } } private void TickNativeFeatures() { //IL_00c6: 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) if (dayEndOverlay != null) { dayEndOverlay.Tick(); } if (nativeFeatureFaulted) { return; } try { if (roundTravel != null) { roundTravel.Tick(); } if (gunShop != null) { gunShop.Tick(); } if (neckwear != null) { neckwear.Tick(); } if (NetworkServer.active && ui != null && ui.IsOpen && bodyRepair != null) { string note; bool flag = bodyRepair.CanRepair(out note); if (flag != bodyRepairAvailability || note != bodyRepairAvailabilityHint) { bodyRepairAvailability = flag; bodyRepairAvailabilityHint = note; Changed(); } } SyncLocalNpcDeaths(); if (npcCombat != null) { ChaosNpcCombat chaosNpcCombat = npcCombat; Scene activeScene = SceneManager.GetActiveScene(); chaosNpcCombat.Tick(((Scene)(ref activeScene)).name == "CasinoScene" && CurrentNpcDayActive()); } if (ui != null) { ui.MaintainNativeChallengePresentation(); } BindCachedNativeUpgradePresentation(); } catch (Exception ex) { nativeFeatureFaulted = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Native additions held after an integration error: " + ex.GetBaseException().Message)); if (ui != null) { ui.ReleaseNativeChallengePresentation(); } } } internal void BindNativeChallenge(ChallengeManager manager) { if (ui != null && !((Object)(object)manager == (Object)null)) { FieldInfo nativeChallengeParent = NativeChallengeParent; if (nativeChallengeParent != null) { ChaosUi chaosUi = ui; object? value = nativeChallengeParent.GetValue(manager); chaosUi.BindNativeChallengePresentation((Transform)((value is Transform) ? value : null)); } } } private void BindCachedNativeUpgradePresentation() { UpgradeUI val = (UpgradeUI)((NativeUpgradeInstance == null) ? null : /*isinst with value type is only supported in some contexts*/); if (!object.ReferenceEquals(val, nativeUpgradePresentation)) { BindNativeUpgradePresentation(val); } } internal void BindNativeUpgradePresentation(UpgradeUI manager) { nativeUpgradePresentation = manager; if (ui != null) { ui.BindNativeUpgradePresentation((Transform)(((Object)(object)manager == (Object)null || NativeUpgradeParent == null) ? null : /*isinst with value type is only supported in some contexts*/)); } } internal bool AwardNpcKill(NetworkConnectionToClient shooter, string key) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Invalid comparison between Unknown and I4 //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_009b: 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_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) GameManager instance = NetworkSingleton.Instance; if (!NetworkServer.active || !NativeFeaturesAllowed() || !CurrentNpcDayActive() || (Object)(object)instance == (Object)null || (int)instance.state != 1 || !instance.HasDayStarted || engine.State.Day != instance.daysPassed + 1 || (Object)(object)NetworkSingleton.Instance == (Object)null || originalQuota <= BigNumber.Zero || !SessionIdentity.TryResolve(SessionPlayerProof(shooter), out var player)) { return false; } BigNumber val = instance.currentQuota / originalQuota; double factor = ((BigNumber)(ref val)).ToDouble(); bool accepted = false; Safe(delegate { List list = engine.AwardNpcKill(key, player, factor); if (list.Count != 0 || engine.RecordNpcDeath(key)) { accepted = true; if (list.Count > 0) { Apply(list); } Changed(); SaveCheckpoint(GetSaveData()); } }); return accepted; } private static void ValidateNpcSnapshot(ViewEnvelope envelope) { if (!NpcPanicPolicy.ValidEvent(envelope.NpcPanic)) { throw new InvalidDataException("Invalid NPC panic snapshot."); } if (envelope.NpcKilledKeys == null || envelope.NpcKilledKeys.Length > 512 || envelope.NpcDayToken == null || envelope.NpcDayToken.Length > 128) { throw new InvalidDataException("Invalid NPC death snapshot."); } HashSet hashSet = new HashSet(); string[] npcKilledKeys = envelope.NpcKilledKeys; foreach (string text in npcKilledKeys) { if (!NpcCombatPolicy.ValidKey(text) || !hashSet.Add(text)) { throw new InvalidDataException("Invalid NPC death key."); } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) || char.IsSurrogate(c)) { throw new InvalidDataException("Invalid NPC death key text."); } } } if (envelope.NpcPanic != null && !hashSet.Contains(envelope.NpcPanic.DeathKey)) { throw new InvalidDataException("NPC panic requires a confirmed death."); } } private void OnGUI() { if (gunReload != null) { gunReload.DrawHud(); } } private void InitializePoliceBail() { policeBailJournal = new PoliceBailJournal(Paths.ConfigPath); policeBailHud = new ChaosPoliceBailHud(((Component)this).transform); network.BailContinueRequested += ReceivePoliceBailContinue; InputSystem.onAfterUpdate += CapturePoliceBailContinue; policeBailInputSubscribed = true; } private static string PoliceChapterKey() { if (!Chainloader.PluginInfos.TryGetValue("co.oasistechnologies.gwyf.level2casino", out var value)) { return "native"; } try { if (value == null || (Object)(object)value.Instance == (Object)null) { return null; } PropertyInfo property = ((object)value.Instance).GetType().GetProperty("PoliceBailChapterKey", BindingFlags.Instance | BindingFlags.Public); if (property == null || property.PropertyType != typeof(string) || !property.CanRead || property.GetIndexParameters().Length != 0) { return null; } string text = property.GetValue(value.Instance, null) as string; if (text == "native") { return text; } if (text == null || text.Length != 64) { return null; } string text2 = text; foreach (char c in text2) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { return null; } } return text; } catch { return null; } } private static string PoliceFinancialDayKey(SaveData data, int day, string chapter, string epoch) { return ChaosSave.Hash(data.saveName + "/" + data.seed.ToString(CultureInfo.InvariantCulture) + "/" + day.ToString(CultureInfo.InvariantCulture) + "/" + chapter + "/" + epoch); } private static string PoliceOriginalCashKey(string dayKey, BigNumber cash) { return ChaosSave.Hash(dayKey + "/" + cash.Mantissa.ToString("R", CultureInfo.InvariantCulture) + "/" + cash.Exponent.ToString(CultureInfo.InvariantCulture)); } private bool ValidPoliceBailState(PoliceBailJournalState value, SaveData current, int day, string chapter) { //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) if (value == null || current == null || value.Schema != 1 || value.SaveName != current.saveName || value.Seed != current.seed || value.Day != day || value.ChapterKey != chapter || string.IsNullOrEmpty(value.RunEpoch) || value.RunEpoch.Length != 32 || value.FinancialDayKey != PoliceFinancialDayKey(current, day, chapter, value.RunEpoch) || value.Incidents == null || value.Incidents.Length > 256 || value.LastRequestId < 0 || value.LastRequestId >= long.MaxValue || value.LastRevision < 0 || value.LastRevision >= long.MaxValue) { return false; } if (double.IsNaN(value.OriginalCashMantissa) || double.IsInfinity(value.OriginalCashMantissa) || value.OriginalCashMantissa < 0.0) { return false; } BigNumber val = default(BigNumber); ((BigNumber)(ref val))..ctor(value.OriginalCashMantissa, value.OriginalCashExponent); if (!PoliceBailCash.Usable(val) || value.OriginalCashKey != PoliceOriginalCashKey(value.FinancialDayKey, val)) { return false; } PoliceBailIncidentRecord[] incidents = value.Incidents; foreach (PoliceBailIncidentRecord policeBailIncidentRecord in incidents) { if (policeBailIncidentRecord == null || policeBailIncidentRecord.Player == 0 || policeBailIncidentRecord.Incident <= 0 || string.IsNullOrEmpty(policeBailIncidentRecord.Generation) || policeBailIncidentRecord.Generation.Length != 32) { return false; } } if (string.IsNullOrEmpty(value.RestartNativeJson) || Encoding.UTF8.GetByteCount(value.RestartNativeJson) > 65536 || value.RestartNativeHash != ChaosSave.Hash(value.RestartNativeJson)) { return false; } SaveData val2 = JsonUtility.FromJson(value.RestartNativeJson); if (val2 == null || val2.saveName != current.saveName || val2.seed != current.seed || val2.daysPassed + 1 != day) { return false; } if (value.Receipt != null && (value.Receipt.FinancialDayKey != value.FinancialDayKey || value.Receipt.StartCashSnapshotKey != value.OriginalCashKey)) { return false; } if (value.Receipt != null) { PoliceBailSnapshot receipt = value.Receipt; if (receipt.RequestId <= 0 || receipt.RequestId >= long.MaxValue || receipt.Revision <= 0 || receipt.Revision >= long.MaxValue || receipt.Player == 0 || receipt.Incident <= 0 || receipt.FeePercent != 10 || receipt.RemainingPercent != 90 || (receipt.Reason != PoliceBailReason.Arrest && receipt.Reason != PoliceBailReason.Surrender) || receipt.Players == null || receipt.Players.Length == 0 || receipt.Players.Length > 6 || receipt.ContinuedPlayers == null || receipt.WaivedPlayers == null || (receipt.Phase != PoliceBailPhase.Reserved && receipt.Phase != PoliceBailPhase.AwaitingHome && receipt.Phase != PoliceBailPhase.WaitingForContinue && receipt.Phase != PoliceBailPhase.Completed && receipt.Phase != PoliceBailPhase.Held)) { return false; } HashSet hashSet = new HashSet(); ulong[] players = receipt.Players; foreach (ulong num in players) { if (num == 0 || !hashSet.Add(num)) { return false; } } if (!hashSet.Contains(receipt.Player)) { return false; } HashSet hashSet2 = new HashSet(); ulong[] continuedPlayers = receipt.ContinuedPlayers; foreach (ulong item in continuedPlayers) { if (!hashSet.Contains(item) || !hashSet2.Add(item)) { return false; } } ulong[] waivedPlayers = receipt.WaivedPlayers; foreach (ulong item2 in waivedPlayers) { if (!hashSet.Contains(item2) || !hashSet2.Add(item2)) { return false; } } if (receipt.Phase == PoliceBailPhase.Completed && hashSet2.Count != hashSet.Count) { return false; } } return true; } private void RememberPoliceBailBasis(SaveData starting, GameManager manager) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_0138: Unknown result type (might be due to invalid IL or missing references) string text = PoliceChapterKey(); if (starting == null || (Object)(object)manager == (Object)null || text == null) { return; } int num = manager.daysPassed + 1; if (ValidPoliceBailState(policeBailState, starting, num, text)) { return; } PoliceBailJournalState policeBailJournalState = ((policeBailJournal == null) ? null : policeBailJournal.Load(starting.saveName, starting.seed)); if (policeBailJournalState != null && policeBailJournalState.Day == num && policeBailJournalState.ChapterKey == text && !ValidPoliceBailState(policeBailJournalState, starting, num, text)) { throw new InvalidDataException("Same-day bail journal is invalid; original cash cannot be replaced."); } if (ValidPoliceBailState(policeBailJournalState, starting, num, text)) { RestorePoliceBailState(policeBailJournalState); return; } BigNumber money = starting.GetMoney(); if (!PoliceBailCash.Usable(money)) { throw new InvalidDataException("Original day-start cash is unavailable."); } string text2 = System.Guid.NewGuid().ToString("N"); string text3 = PoliceFinancialDayKey(starting, num, text, text2); policeBailState = new PoliceBailJournalState { SaveName = starting.saveName, Seed = starting.seed, Day = num, RunEpoch = text2, ChapterKey = text, FinancialDayKey = text3, OriginalCashMantissa = money.Mantissa, OriginalCashExponent = money.Exponent, OriginalCashKey = PoliceOriginalCashKey(text3, money), RestartNativeJson = dayStartNativeJson, RestartNativeHash = ChaosSave.Hash(dayStartNativeJson) }; policeBail = new PoliceBailCoordinator(); policeBailPlan = null; policeBailResume = false; policeBail.BindDay(session, text3, Time.unscaledTime); WritePoliceBailJournal(); } private void LoadPoliceBailCheckpoint(ChaosCheckpoint checkpoint, SaveData data) { //IL_016d: Unknown result type (might be due to invalid IL or missing references) //IL_0235: Unknown result type (might be due to invalid IL or missing references) string text = PoliceChapterKey(); GameManager instance = NetworkSingleton.Instance; if (text == null || (Object)(object)instance == (Object)null || data == null) { return; } int num = instance.daysPassed + 1; PoliceBailJournalState value = null; if (checkpoint != null && !string.IsNullOrEmpty(checkpoint.PoliceBailJson)) { value = ChaosJson.Read(checkpoint.PoliceBailJson); } PoliceBailJournalState policeBailJournalState = ((policeBailJournal == null) ? null : policeBailJournal.Load(data.saveName, data.seed)); if (policeBailJournalState != null && policeBailJournalState.Day == num && policeBailJournalState.ChapterKey == text && !ValidPoliceBailState(policeBailJournalState, data, num, text)) { throw new InvalidDataException("Same-day bail recovery journal is invalid."); } if (ValidPoliceBailState(policeBailJournalState, data, num, text)) { value = policeBailJournalState; } if (ValidPoliceBailState(value, data, num, text)) { RestorePoliceBailState(value); } else if (checkpoint != null && checkpoint.HasNativeBaseline && checkpoint.NativeDays == data.daysPassed && checkpoint.NativeDays == instance.daysPassed && checkpoint.DayStartNativeDay == num && !string.IsNullOrEmpty(checkpoint.DayStartNativeJson) && engine != null && engine.State.DayOpen && !engine.State.DayEnded && engine.State.Day == num) { BigNumber val = default(BigNumber); ((BigNumber)(ref val))..ctor(checkpoint.NativeStartBalanceMantissa, checkpoint.NativeStartBalanceExponent); if (!PoliceBailCash.Usable(val)) { throw new InvalidDataException("The paired original cash baseline is invalid."); } SaveData val2 = JsonUtility.FromJson(checkpoint.DayStartNativeJson); if (val2 == null || val2.saveName != data.saveName || val2.seed != data.seed || val2.daysPassed + 1 != num) { throw new InvalidDataException("The paired restart snapshot is not the current day."); } string text2 = System.Guid.NewGuid().ToString("N"); string text3 = PoliceFinancialDayKey(data, num, text, text2); policeBailState = new PoliceBailJournalState { SaveName = data.saveName, Seed = data.seed, Day = num, ChapterKey = text, RunEpoch = text2, FinancialDayKey = text3, OriginalCashKey = PoliceOriginalCashKey(text3, val), OriginalCashMantissa = val.Mantissa, OriginalCashExponent = val.Exponent, RestartNativeJson = checkpoint.DayStartNativeJson, RestartNativeHash = ChaosSave.Hash(checkpoint.DayStartNativeJson) }; policeBail = new PoliceBailCoordinator(); policeBail.BindDay(session, text3, Time.unscaledTime); WritePoliceBailJournal(); } } private void RestorePoliceBailState(PoliceBailJournalState value) { policeBailState = ChaosJson.Read(ChaosJson.Write(value)); List list = new List(); PoliceBailIncidentRecord[] incidents = policeBailState.Incidents; foreach (PoliceBailIncidentRecord policeBailIncidentRecord in incidents) { if (policeBailIncidentRecord.Generation == policeBailGeneration) { list.Add(policeBailIncidentRecord); } } policeBailState.Incidents = list.ToArray(); if (string.IsNullOrEmpty(dayStartNativeJson)) { dayStartNativeJson = value.RestartNativeJson; dayStartNativeDay = value.Day; } policeBail = new PoliceBailCoordinator(); policeBailPlan = null; policeBail.BindDay(session, value.FinancialDayKey, Time.unscaledTime); if (!policeBail.RestoreCounters(value.LastRequestId, value.LastRevision)) { throw new InvalidDataException("Invalid bail request sequence."); } PoliceBailIncidentRecord[] incidents2 = value.Incidents; foreach (PoliceBailIncidentRecord policeBailIncidentRecord2 in incidents2) { if (policeBailIncidentRecord2.Generation == policeBailGeneration) { policeBail.RestoreCommitted(policeBailIncidentRecord2.Player, policeBailIncidentRecord2.Incident, value.FinancialDayKey); } } PoliceBailSnapshot receipt = value.Receipt; policeBailResume = false; if (receipt != null && (receipt.Phase == PoliceBailPhase.Reserved || receipt.Phase == PoliceBailPhase.AwaitingHome || receipt.Phase == PoliceBailPhase.WaitingForContinue || receipt.Phase == PoliceBailPhase.Held)) { if (!policeBail.RestoreReceipt(receipt, Time.unscaledTime, out policeBailPlan)) { throw new InvalidDataException("Bail receipt recovery failed validation."); } policeBailResume = receipt.Phase == PoliceBailPhase.Reserved || receipt.Phase == PoliceBailPhase.AwaitingHome; } } private void WritePoliceBailJournal() { if (policeBailState != null && policeBailJournal != null) { PoliceBailSnapshot policeBailSnapshot = policeBail.Snapshot(); policeBailState.LastRequestId = policeBail.LastRequestId; policeBailState.LastRevision = policeBail.CurrentRevision; policeBailState.Receipt = ((policeBailSnapshot.Phase == PoliceBailPhase.None || policeBailSnapshot.Phase == PoliceBailPhase.Cancelled) ? null : policeBailSnapshot); policeBailJournal.Write(policeBailState); policeBail.DrainEvents(); } } private string PoliceBailHold(ulong player) { //IL_004d: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Invalid comparison between Unknown and I4 if (!NetworkServer.active || !compatible || network == null || !network.ReadyForChaos || !string.IsNullOrEmpty(holdReason)) { return "Bail is unavailable."; } GameManager instance = NetworkSingleton.Instance; SaveData saveData = GetSaveData(); if ((Object)(object)instance == (Object)null || (int)instance.state != 1 || saveData == null || (Object)(object)NetworkSingleton.Instance == (Object)null || sessionResetActive || NativeTransitioning || PoliceBailBlocking) { return "Wait for the current transition."; } if (player == 0 || !TryConnectedVoters(out var players, out var _) || Array.IndexOf(players, player) < 0) { return "The connected crew is not ready."; } string text = PoliceChapterKey(); if (text == null || !ValidPoliceBailState(policeBailState, saveData, instance.daysPassed + 1, text) || dayStartNativeDay != instance.daysPassed + 1 || string.IsNullOrEmpty(dayStartNativeJson)) { return "Original starting cash will be available from the next full casino day."; } if (restartVote != null && (restartVote.State.Status == VoteStatus.Pending || restartVote.State.Status == VoteStatus.Passed)) { return "Finish the restart vote first."; } return null; } public string PoliceBailQuote(ulong player) { //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) try { return (PoliceBailHold(player) == null) ? MoneyFormatter.FormatWithDollar(PoliceBailCash.Fee(PoliceOriginalCash), false) : null; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Bail quote held: " + ex.GetType().Name)); return null; } } public bool QueuePoliceBail(ulong player, int incident, string reason) { PoliceBailReason reason2; if (reason == "Surrender") { reason2 = PoliceBailReason.Surrender; } else { if (!(reason == "Arrest")) { return false; } reason2 = PoliceBailReason.Arrest; } if (incident <= 0 || PoliceBailHold(player) != null || policeBailState.Incidents.Length >= 256) { return false; } if (!TryConnectedVoters(out var players, out var _)) { return false; } if (!policeBail.TryPrepare(player, incident, reason2, policeBailState.OriginalCashKey, players, Time.unscaledTime, out var plan)) { return false; } policeBailPlan = plan; policeBailState.ReceiptGeneration = policeBailGeneration; policeBailState.RestartNativeJson = dayStartNativeJson; policeBailState.RestartNativeHash = ChaosSave.Hash(dayStartNativeJson); try { WritePoliceBailJournal(); } catch (Exception ex) { bool flag = false; try { PoliceBailJournalState policeBailJournalState = policeBailJournal.Load(policeBailState.SaveName, policeBailState.Seed); flag = policeBailJournalState != null && policeBailJournalState.OriginalCashKey == policeBailState.OriginalCashKey && policeBailJournalState.Receipt != null && policeBailJournalState.Receipt.RequestId == plan.RequestId && policeBailJournalState.Receipt.Player == player && policeBailJournalState.Receipt.Incident == incident && policeBailJournalState.Receipt.Phase == PoliceBailPhase.Reserved && policeBailJournalState.ReceiptGeneration == policeBailGeneration; } catch { } if (!flag) { HoldPoliceBail("The bail request could not be verified. Starting cash has not been changed. A recovery receipt was retained."); ((BaseUnityPlugin)this).Logger.LogError((object)("Bail request held: " + ex)); return false; } policeBail.DrainEvents(); ((BaseUnityPlugin)this).Logger.LogWarning((object)("Bail reservation verified after a journal readback interruption: " + ex.GetType().Name)); } sessionControlNote = "Bail accepted. Finish the current casino games to return to bed."; Changed(); network.Broadcast(); Present(); RefreshOverlayInput(); return true; } private void TickPoliceBail() { //IL_01b3: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active || policeBailState == null || !policeBail.Active) { return; } PoliceBailSnapshot policeBailSnapshot = policeBail.Snapshot(); ulong[] players = policeBailSnapshot.Players; foreach (ulong num in players) { bool flag = false; foreach (NetworkConnectionToClient value in NetworkServer.connections.Values) { PlayerProfile val = ((value == null || (Object)(object)((NetworkConnection)value).identity == (Object)null) ? null : ((Component)((NetworkConnection)value).identity).GetComponent()); if ((Object)(object)val != (Object)null && val.steamId == num) { flag = true; break; } } if (!flag && policeBail.WaiveDisconnected(policeBailSnapshot.RequestId, num, Time.unscaledTime)) { PoliceBailChanged(); } } if (sessionResetActive || policeBailSnapshot.Phase == PoliceBailPhase.Held || policeBailSnapshot.Phase == PoliceBailPhase.WaitingForContinue || (policeBailSnapshot.Phase != PoliceBailPhase.Reserved && (!policeBailResume || policeBailSnapshot.Phase != PoliceBailPhase.AwaitingHome))) { return; } GameManager instance = NetworkSingleton.Instance; SaveData saveData = GetSaveData(); string text = PoliceChapterKey(); if ((Object)(object)instance == (Object)null || saveData == null || text == null || NativeTransitioning || !network.ReadyForChaos) { return; } if (!ValidPoliceBailState(policeBailState, saveData, instance.daysPassed + 1, text)) { HoldPoliceBail("Bail recovery does not match the current day."); } else { if (HasPendingWagers || AnyCasinoGameRunning() || !TryConnectedVoters(out var _, out var _)) { return; } try { SaveData snapshot = PoliceBailCash.Prepare(policeBailState.RestartNativeJson, saveData, policeBailState.Day, PoliceOriginalCash); if (policeBailSnapshot.Phase == PoliceBailPhase.Reserved) { if (!policeBail.MarkRestartQueued(policeBailPlan, Time.unscaledTime)) { throw new InvalidOperationException("Bail reset was already consumed."); } List list = new List(policeBailState.Incidents); list.Add(new PoliceBailIncidentRecord { Generation = policeBailGeneration, Player = policeBailSnapshot.Player, Incident = policeBailSnapshot.Incident }); policeBailState.Incidents = list.ToArray(); WritePoliceBailJournal(); } policeBailResume = false; StartSessionReload(snapshot, fresh: false); sessionControlNote = "Bail paid. Returning everyone to bed on day " + policeBailState.Day + "."; PoliceBailChanged(); } catch (Exception ex) { HoldPoliceBail("The bail reset could not finish. A recovery backup was retained."); ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } } private void PoliceBailHomeReady() { if (policeBailState != null && policeBailPlan != null && policeBail.MarkHomeReady(policeBailPlan.RequestId, policeBailState.FinancialDayKey, Time.unscaledTime)) { PoliceBailChanged(); } } private void EnsurePoliceBailCheckpoint(SaveData data) { if (policeBailState != null) { if (!string.IsNullOrEmpty(holdReason) || data == null) { throw new IOException("The bail checkpoint could not be saved safely."); } string text = NativePath(data.saveName); if (text == null || !File.Exists(text)) { throw new IOException("The native bail save is unavailable."); } string reason; ChaosCheckpoint chaosCheckpoint = saves.Load(data.saveName, File.ReadAllText(text), out reason); if (reason != null || chaosCheckpoint == null || string.IsNullOrEmpty(chaosCheckpoint.PoliceBailJson) || ChaosSave.Hash(chaosCheckpoint.PoliceBailJson) != ChaosSave.Hash(ChaosJson.Write(policeBailState))) { throw new IOException("The exact native bail save and companion receipt did not match."); } } } private void HoldPoliceBail(string note) { if (policeBailPlan != null) { policeBail.Hold(policeBailPlan, Time.unscaledTime); } sessionControlNote = note; try { WritePoliceBailJournal(); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Bail recovery journal unavailable: " + ex)); } Changed(); if (network != null) { network.Broadcast(); } Present(); RefreshOverlayInput(); } private void PoliceBailChanged() { if (!policeBail.Active) { policeBailClosedFrame = Time.frameCount; } WritePoliceBailJournal(); SaveCheckpoint(GetSaveData()); if (!policeBail.Active && policeBailState.ReceiptGeneration != policeBailGeneration) { policeBail = new PoliceBailCoordinator(); policeBail.BindDay(session, policeBailState.FinancialDayKey, Time.unscaledTime); policeBail.RestoreCounters(policeBailState.LastRequestId, policeBailState.LastRevision); PoliceBailIncidentRecord[] incidents = policeBailState.Incidents; foreach (PoliceBailIncidentRecord policeBailIncidentRecord in incidents) { if (policeBailIncidentRecord.Generation == policeBailGeneration) { policeBail.RestoreCommitted(policeBailIncidentRecord.Player, policeBailIncidentRecord.Incident, policeBailState.FinancialDayKey); } } } Changed(); network.Broadcast(); Present(); RefreshOverlayInput(); } private void ReceivePoliceBailContinue(NetworkConnectionToClient connection, ChaosNetwork.BailContinue request) { if (!SessionIdentity.TryResolve(SessionPlayerProof(connection), out var steamId) || !policeBail.TryContinue(request.RequestId, steamId, request.Revision, Time.unscaledTime)) { return; } try { PoliceBailChanged(); } catch (Exception ex) { HoldPoliceBail("Bail receipt persistence needs recovery."); ((BaseUnityPlugin)this).Logger.LogError((object)ex); } } private void CapturePoliceBailContinue() { Keyboard current = Keyboard.current; if (!PoliceBailBlocksInput) { policeBailKeyReleased = false; } else if (current != null) { if (!((ButtonControl)current.eKey).isPressed) { policeBailKeyReleased = true; } if (policeBailKeyReleased && ((ButtonControl)current.eKey).wasPressedThisFrame) { policeBailELatched = true; } } } private void TickPoliceBailInput() { bool policeBailReceiptVisible = PoliceBailReceiptVisible; if (policeBailLastBlocked && !policeBailReceiptVisible) { policeBailClosedFrame = Time.frameCount; } policeBailLastBlocked = policeBailReceiptVisible; bool flag = policeBailELatched; policeBailELatched = false; if (flag && policeBailReceiptVisible && !(Time.unscaledTime < policeBailNextAck) && network != null) { UiState uiState = BuildView(); ulong value = LocalPlayerId(); if (uiState.PoliceBailCanContinue && uiState.PoliceBailCrew != null && Array.IndexOf(uiState.PoliceBailCrew, value) >= 0 && (uiState.PoliceBailContinued == null || Array.IndexOf(uiState.PoliceBailContinued, value) < 0)) { policeBailNextAck = Time.unscaledTime + 0.3f; network.RequestBailContinue(uiState.PoliceBailRequestId, uiState.PoliceBailRevision); } } } private void AddPoliceBailView(UiState view) { //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) if (policeBailState != null && policeBail != null) { PoliceBailSnapshot policeBailSnapshot = policeBail.Snapshot(); view.PoliceBailVisible = policeBailSnapshot.Phase == PoliceBailPhase.AwaitingHome || policeBailSnapshot.Phase == PoliceBailPhase.WaitingForContinue || policeBailSnapshot.Phase == PoliceBailPhase.Held; view.PoliceBailCanContinue = policeBailSnapshot.Phase == PoliceBailPhase.WaitingForContinue; view.PoliceBailRequestId = policeBailSnapshot.RequestId; view.PoliceBailRevision = policeBailSnapshot.Revision; view.PoliceBailCrew = policeBailSnapshot.Players; view.PoliceBailContinued = policeBailSnapshot.ContinuedPlayers; view.PoliceBailWaived = policeBailSnapshot.WaivedPlayers; view.PoliceBailDay = policeBailState.Day; view.PoliceBailFee = MoneyFormatter.FormatWithDollar(PoliceBailCash.Fee(PoliceOriginalCash), false); view.PoliceBailOriginal = MoneyFormatter.FormatWithDollar(PoliceOriginalCash, false); view.PoliceBailRemaining = MoneyFormatter.FormatWithDollar(PoliceBailCash.Remaining(PoliceOriginalCash), false); view.PoliceBailStatus = ((policeBailSnapshot.Phase == PoliceBailPhase.Held) ? (sessionControlNote ?? "Recovery backup retained. Ask the host to review the game log.") : (view.PoliceBailCanContinue ? "You are back in bed. Press E to continue." : "Returning everyone to bed...")); } } private static void ValidatePoliceBailView(UiState view) { if (!view.PoliceBailVisible) { return; } if (view.PoliceBailRequestId <= 0 || view.PoliceBailRevision <= 0 || view.PoliceBailDay < 1 || view.PoliceBailCrew == null || view.PoliceBailCrew.Length == 0 || view.PoliceBailCrew.Length > 6 || view.PoliceBailContinued == null || view.PoliceBailWaived == null) { throw new InvalidDataException("Invalid bail receipt."); } HashSet hashSet = new HashSet(); ulong[] policeBailCrew = view.PoliceBailCrew; foreach (ulong num in policeBailCrew) { if (num == 0 || !hashSet.Add(num)) { throw new InvalidDataException("Invalid bail crew."); } } HashSet hashSet2 = new HashSet(); ulong[] policeBailContinued = view.PoliceBailContinued; foreach (ulong item in policeBailContinued) { if (!hashSet.Contains(item) || !hashSet2.Add(item)) { throw new InvalidDataException("Invalid bail acknowledgement."); } } ulong[] policeBailWaived = view.PoliceBailWaived; foreach (ulong item2 in policeBailWaived) { if (!hashSet.Contains(item2) || !hashSet2.Add(item2)) { throw new InvalidDataException("Invalid bail disconnect receipt."); } } string[] array = new string[4] { view.PoliceBailFee, view.PoliceBailOriginal, view.PoliceBailRemaining, view.PoliceBailStatus }; foreach (string text in array) { if (text == null || text.Length > 512) { throw new InvalidDataException("Invalid bail receipt wording."); } } } private Action CapturePoliceBailRecovery() { string json = ((policeBailState == null) ? null : ChaosJson.Write(policeBailState)); bool resume = policeBailResume; string generation = policeBailGeneration; return delegate { policeBailGeneration = generation; if (json == null) { ResetPoliceBailRun(); policeBailGeneration = generation; } else { RestorePoliceBailState(ChaosJson.Read(json)); policeBailResume = resume; } }; } private void BeginPoliceBailFresh(SaveData snapshot) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) string text = PoliceChapterKey(); if (text == null) { throw new InvalidOperationException("Level2 starting progress is not ready for a fresh run."); } ResetPoliceBailRun(); string text2 = JsonUtility.ToJson((object)snapshot, true); if (Encoding.UTF8.GetByteCount(text2) > 65536) { throw new InvalidDataException("Fresh starting progress exceeds its limit."); } string text3 = System.Guid.NewGuid().ToString("N"); int day = snapshot.daysPassed + 1; BigNumber money = snapshot.GetMoney(); string text4 = PoliceFinancialDayKey(snapshot, day, text, text3); policeBailState = new PoliceBailJournalState { SaveName = snapshot.saveName, Seed = snapshot.seed, Day = day, ChapterKey = text, RunEpoch = text3, FinancialDayKey = text4, OriginalCashMantissa = money.Mantissa, OriginalCashExponent = money.Exponent, OriginalCashKey = PoliceOriginalCashKey(text4, money), RestartNativeJson = text2, RestartNativeHash = ChaosSave.Hash(text2) }; policeBail.BindDay(session, text4, Time.unscaledTime); WritePoliceBailJournal(); } private void ResetPoliceBailRun() { policeBail = new PoliceBailCoordinator(); policeBailPlan = null; policeBailState = null; policeBailResume = false; policeBailGeneration = System.Guid.NewGuid().ToString("N"); } private void DisposePoliceBail() { if (policeBailInputSubscribed) { InputSystem.onAfterUpdate -= CapturePoliceBailContinue; } policeBailInputSubscribed = false; if (network != null) { network.BailContinueRequested -= ReceivePoliceBailContinue; } if (policeBailHud != null) { policeBailHud.Dispose(); } } private void InitializeStats() { statsArt = new ChaosStatsArt(); statsArt.ArtworkChanged += ui.RefreshStatsArtwork; ui.ConfigureStats(RequestStats, statsArt.Get, statsArt.Source); network.StatsQueryReceived += ReceiveStatsQuery; network.StatsViewReceived += ReceiveStatsView; network.StatsPaidReceived += ReceiveStatsPaid; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Stats: paid settled wagers; big win is payout >= 3x bet. Tracking starts with 0.5.0. " + StatsLedger.SelfTest())); } private static ulong StatsLocalPlayer() { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (!SteamManager.Initialized) { return 0uL; } return SteamUser.GetSteamID().m_SteamID; } private static StatsAmount StatsCash(BigNumber amount) { return new StatsAmount(amount.Mantissa, amount.Exponent); } private void EnsureStatsLifetime() { ulong num = StatsLocalPlayer(); if (num != 0 && (statsLifetime == null || statsLifetime.State.Owner != num)) { QueueStatsPersistence(); string absolutePath = Path.Combine(StatsDirectory, "lifetime-" + num.ToString(CultureInfo.InvariantCulture) + ".json"); StatsStore statsStore = new StatsStore(absolutePath); string reason; StatsState statsState = statsStore.Load(out reason); StatsLedger statsLedger = new StatsLedger(num, ""); if (statsState != null) { statsLedger.Restore(statsState); } statsLifetime = statsLedger; statsLifetimeStore = statsStore; statsLifetimePath = absolutePath; statsLifetimeSaved = statsLedger.State.Revision; } } private void EnsureStatsRun() { if (!NetworkServer.active) { return; } SaveData saveData = GetSaveData(); ulong num = StatsLocalPlayer(); if (saveData == null || string.IsNullOrEmpty(saveData.saveName) || num == 0) { return; } string text = ChaosSave.Hash(num.ToString(CultureInfo.InvariantCulture) + ":" + saveData.saveName); if (statsRun != null && statsSaveKey == text && !statsForceFresh) { return; } QueueStatsPersistence(); if (!statsForceFresh && statsOpenRuns.TryGetValue(text, out var value)) { statsRun = value; statsRunPath = Path.Combine(StatsDirectory, "run-" + value.State.RunId + ".json"); statsRunStore = new StatsStore(statsRunPath); statsSaveKey = text; statsRunSaved = -1L; statsIndexPath = Path.Combine(StatsDirectory, "save-" + text + ".json"); statsIndexStore = new StatsStore(statsIndexPath); statsIndexDirty = true; statsWagers.Clear(); return; } StatsStore statsStore = new StatsStore(Path.Combine(StatsDirectory, "save-" + text + ".json")); string reason; StatsState statsState = (statsForceFresh ? null : statsStore.Load(out reason)); string text2 = ((statsState == null) ? System.Guid.NewGuid().ToString("N") : statsState.RunId); if (statsState != null && (statsState.Owner != 0 || statsState.Revision != 0 || !ValidStatsRunId(text2))) { throw new InvalidDataException("Invalid saved statistics run mapping."); } string absolutePath = Path.Combine(StatsDirectory, "run-" + text2 + ".json"); StatsStore statsStore2 = new StatsStore(absolutePath); StatsState statsState2 = statsStore2.Load(out reason); StatsLedger statsLedger = new StatsLedger(0uL, text2); if (statsState2 != null) { statsLedger.Restore(statsState2); } if (statsState == null && !statsWriter.Enqueue(Path.Combine(StatsDirectory, "save-" + text + ".json"), statsStore, new StatsLedger(0uL, text2).Snapshot())) { throw new IOException("Statistics save queue is busy; wait and refresh."); } statsRun = statsLedger; statsRunStore = statsStore2; statsRunPath = absolutePath; statsSaveKey = text; statsIndexPath = Path.Combine(StatsDirectory, "save-" + text + ".json"); statsIndexStore = statsStore; statsIndexDirty = false; if (statsOpenRuns.Count >= 32 && !statsOpenRuns.ContainsKey(text)) { statsOpenRuns.Clear(); } statsOpenRuns[text] = statsLedger; statsRunSaved = statsLedger.State.Revision; statsForceFresh = false; statsWagers.Clear(); } private static bool ValidStatsRunId(string id) { if (id == null || id.Length != 32) { return false; } foreach (char c in id) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { return false; } } return true; } private void StatsSafe(Action action) { try { statsError = null; action(); } catch (Exception ex) { statsError = "Stats unavailable. Saved history was preserved; check the BepInEx log and try Refresh."; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Stats: " + ex.GetBaseException().Message)); } } private bool StatsTrackingReady(PlayerProfile profile) { if (!compatible || !NetworkServer.active || network == null || !network.ReadyForChaos || (Object)(object)profile == (Object)null || sessionResetActive || NativeTransitioning) { return false; } NetworkConnectionToClient connectionToClient = ((NetworkBehaviour)profile).connectionToClient; if (SessionIdentity.TryResolve(SessionPlayerProof(connectionToClient), out var steamId) && steamId == profile.steamId && (Object)(object)((NetworkConnection)connectionToClient).identity != (Object)null) { return object.ReferenceEquals(((Component)((NetworkConnection)connectionToClient).identity).GetComponent(), profile); } return false; } private void ObserveStatsCharge(BigNumber amount, PlayerProfile profile, ChangeType kind, bool success) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!success || BlockNewWagers || (int)kind != 0 || amount >= BigNumber.Zero || (Object)(object)BetContext == (Object)null || !StatsTrackingReady(profile)) { return; } StatsSafe(delegate { //IL_003d: 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_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected I4, but got Unknown EnsureStatsRun(); if (statsRun != null) { statsWagers.Add(profile.steamId, ((Object)BetContext).GetInstanceID(), (int)BetContext.GameType, StatsCash(-amount)); } StatsSafe(EnsureStatsLifetime); }); } private void CancelStatsWagers(GameBase game) { if (NetworkServer.active && (Object)(object)game != (Object)null) { statsWagers.Cancel(((Object)game).GetInstanceID()); } } private void ObserveStatsResult(BigNumber bet, BigNumber payout, PlayerProfile profile, CasinoGameType game) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //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) if (bet <= BigNumber.Zero || payout < BigNumber.Zero || (Object)(object)StatsResultContext == (Object)null || !StatsTrackingReady(profile)) { return; } StatsSafe(delegate { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected I4, but got Unknown //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Expected I4, but got Unknown //IL_011d: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) if (statsRun != null && statsWagers.Consume(profile.steamId, ((Object)StatsResultContext).GetInstanceID(), (int)game, StatsCash(bet)) && statsPaidSequence != long.MaxValue) { string statsStream = network.StatsStream; if (ValidStatsRunId(statsStream)) { StatsSettlement statsSettlement = new StatsSettlement { ReceiptId = statsStream + ":" + (++statsPaidSequence).ToString(CultureInfo.InvariantCulture), RunId = statsRun.State.RunId, Stream = statsStream, Sequence = statsPaidSequence, Player = profile.steamId, PlayerName = StatsLedger.CleanName(profile.playerName), Game = (int)game, Bet = StatsCash(bet), Payout = StatsCash(payout) }; if (!statsRun.ApplyPaidSettlement(statsSettlement, out var reason)) { statsError = reason; } else if (statsSettlement.Player == StatsLocalPlayer()) { ReceiveStatsPaid(statsSettlement); } else { network.SendStatsPaid(((NetworkBehaviour)profile).connectionToClient, statsSettlement); } } } }); } private void ReceiveStatsPaid(StatsSettlement paid) { StatsSafe(delegate { if (network != null && network.ReadyForChaos && ChaosNetwork.ValidStatsSettlement(paid) && !(paid.Stream != network.StatsStream) && paid.Player == StatsLocalPlayer()) { EnsureStatsLifetime(); if (statsLifetime != null) { statsLifetime.ApplyPaidSettlement(paid, out var _); } } }); } private void NewStatsRun() { QueueStatsPersistence(); statsForceFresh = true; statsWagers.Clear(); } private void ReleaseStatsRun() { QueueStatsPersistence(); statsRun = null; statsRunStore = null; statsRunPath = null; statsSaveKey = null; statsIndexStore = null; statsIndexPath = null; statsIndexDirty = false; statsRunSaved = -1L; statsWagers.Clear(); } private void QueueStatsPersistence() { if (statsIndexDirty && statsRun != null && statsIndexStore != null && statsWriter.Enqueue(statsIndexPath, statsIndexStore, new StatsLedger(0uL, statsRun.State.RunId).Snapshot())) { statsIndexDirty = false; } if (statsLifetime != null && statsLifetimeStore != null && statsLifetime.State.Revision != statsLifetimeSaved && statsWriter.Enqueue(statsLifetimePath, statsLifetimeStore, statsLifetime.Snapshot())) { statsLifetimeSaved = statsLifetime.State.Revision; } if (statsRun != null && statsRunStore != null && statsRun.State.Revision != statsRunSaved && statsWriter.Enqueue(statsRunPath, statsRunStore, statsRun.Snapshot())) { statsRunSaved = statsRun.State.Revision; } } private void TickStats() { if (statsArt != null) { statsArt.Tick(ui != null && ui.StatsVisible); } if (Time.unscaledTime < statsNextSave) { return; } statsNextSave = Time.unscaledTime + 5f; string text = statsWriter.TakeError(); if (text != null) { statsError = "Recent stats were not saved. Saved history was preserved; try Refresh."; statsLifetimeSaved = -1L; statsRunSaved = -1L; statsIndexDirty = true; ((BaseUnityPlugin)this).Logger.LogWarning((object)text); } try { QueueStatsPersistence(); } catch (Exception ex) { statsError = "Recent stats could not be saved; check the BepInEx log."; ((BaseUnityPlugin)this).Logger.LogWarning((object)ex.GetBaseException().Message); } } private void DisposeStats() { StatsSafe(QueueStatsPersistence); statsWriter.Finish(1000); if (statsArt != null) { statsArt.Dispose(); } } private Action CaptureStatsRecovery() { StatsLedger beforeRun = statsRun; StatsStore beforeStore = statsRunStore; StatsStore beforeIndex = statsIndexStore; string beforeIndexPath = statsIndexPath; bool beforeIndexDirty = statsIndexDirty; string beforePath = statsRunPath; string beforeKey = statsSaveKey; string beforeError = statsError; long beforeSaved = statsRunSaved; bool beforeFresh = statsForceFresh; Dictionary beforeOpen = new Dictionary(statsOpenRuns, StringComparer.Ordinal); return delegate { statsRun = beforeRun; statsRunStore = beforeStore; statsRunPath = beforePath; statsSaveKey = beforeKey; statsIndexStore = beforeIndex; statsIndexPath = beforeIndexPath; statsIndexDirty = beforeIndexDirty; statsRunSaved = beforeSaved; statsForceFresh = beforeFresh; statsError = beforeError; statsOpenRuns.Clear(); foreach (KeyValuePair item in beforeOpen) { statsOpenRuns.Add(item.Key, item.Value); } statsWagers.Clear(); }; } private void RequestStats(StatsQuery query) { StatsSafe(delegate { ulong local = StatsLocalPlayer(); if (query.Scope == 1) { EnsureStatsLifetime(); ui.PresentStats(BuildStatsView(query, statsLifetime, local)); } else if (NetworkServer.active) { EnsureStatsRun(); ui.PresentStats(BuildStatsView(query, statsRun, local)); } else if (network == null || !network.RequestStats(query)) { ui.PresentStats(BuildStatsView(query, null, local)); } }); if (!string.IsNullOrEmpty(statsError)) { ui.PresentStats(BuildStatsView(query, null, StatsLocalPlayer())); } } private void ReceiveStatsQuery(StatsQuery query, NetworkConnectionToClient connection) { if (SessionIdentity.TryResolve(SessionPlayerProof(connection), out var local)) { StatsSafe(delegate { EnsureStatsRun(); network.ReplyStats(connection, BuildStatsView(query, statsRun, local)); }); if (!string.IsNullOrEmpty(statsError)) { network.ReplyStats(connection, BuildStatsView(query, null, local)); } } } private void ReceiveStatsView(StatsView view) { if (ui != null && view.LocalPlayerId == StatsLocalPlayer()) { ui.PresentStats(view); } } private StatsView BuildStatsView(StatsQuery query, StatsLedger ledger, ulong local) { List list = new List(); HashSet hashSet = new HashSet(); if (ledger != null) { foreach (StatsRow row in ledger.State.Rows) { if (hashSet.Add(row.Player)) { list.Add(new StatsPlayerOption { Id = row.Player, Name = (string.IsNullOrEmpty(row.PlayerName) ? "Player" : row.PlayerName), LifetimeAvailable = (row.Player == local) }); } } } if (NetworkServer.active && query.Scope == 0) { foreach (NetworkConnectionToClient value in NetworkServer.connections.Values) { if (list.Count < 32 && SessionIdentity.TryResolve(SessionPlayerProof(value), out var steamId) && hashSet.Add(steamId)) { PlayerProfile component = ((Component)((NetworkConnection)value).identity).GetComponent(); list.Add(new StatsPlayerOption { Id = steamId, Name = StatsLedger.CleanName(component.playerName), LifetimeAvailable = (steamId == local) }); } } } if (local != 0 && !hashSet.Contains(local) && list.Count < 32) { list.Add(new StatsPlayerOption { Id = local, Name = "You", LifetimeAvailable = true }); } ulong num = ((query.Scope == 1) ? local : query.PlayerId); StatsView statsView = new StatsView(); statsView.RequestId = query.RequestId; statsView.Scope = query.Scope; statsView.PlayerId = num; statsView.LocalPlayerId = local; statsView.Status = ((!string.IsNullOrEmpty(statsError)) ? statsError : ((ledger == null) ? "Join a compatible lobby to view this run." : "Stats loaded. Refresh after more games.")); statsView.PeriodLabel = ((query.Scope == 1) ? "Your lifetime · all saves and hosts" : "Current run · includes restarted-day attempts"); statsView.PrivacyHint = "Your lifetime stats stay on this computer across runs and hosts."; statsView.Players = list.ToArray(); statsView.Summary = StatsTotalsFor((ledger == null) ? new StatsRow() : ledger.Summarize(num, -1)); statsView.Games = new StatsGameView[17]; StatsView statsView2 = statsView; for (int i = 0; i < statsView2.Games.Length; i++) { statsView2.Games[i] = new StatsGameView { GameId = i, Name = StatsGameName(i), Totals = StatsTotalsFor((ledger == null) ? new StatsRow() : ledger.Summarize(num, i)) }; } return statsView2; } private static string StatsGameName(int game) { string text = Enum.GetName(typeof(CasinoGameType), game) ?? "Casino game"; return Regex.Replace(text.Replace('_', ' '), "([a-z])([A-Z])", "$1 $2"); } private static StatsTotals StatsTotalsFor(StatsRow row) { StatsTotals statsTotals = new StatsTotals(); statsTotals.Played = row.Plays; statsTotals.Wins = row.Wins; statsTotals.Losses = row.Losses; statsTotals.Ties = row.Ties; statsTotals.BigWins = row.BigWins; statsTotals.Net = ((row.Net.Mantissa > 0.0) ? "+" : ((row.Net.Mantissa < 0.0) ? "−" : "")) + StatsMoney(row.Net); statsTotals.PositiveNet = StatsMoney(row.GrossProfit); statsTotals.NegativeNet = StatsMoney(row.GrossLoss); statsTotals.Wagered = StatsMoney(row.Wager); statsTotals.Payout = StatsMoney(row.Payout); statsTotals.BestNetWin = StatsMoney(row.LargestNetWin); statsTotals.LargestBet = StatsMoney(row.LargestStake); statsTotals.WinRate = StatsRate(row.Wins, row.Plays); statsTotals.BigWinRate = StatsRate(row.BigWins, row.Plays); return statsTotals; } private static string StatsRate(long count, long played) { if (played != 0) { return ((double)count / (double)played * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "%"; } return "—"; } private static string StatsMoney(StatsAmount amount) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) amount.Mantissa = Math.Abs(amount.Mantissa); try { string text = MoneyFormatter.FormatWithDollar(new BigNumber(amount.Mantissa, amount.Exponent), false); if (text.Length <= 64) { return text; } } catch { } return "$" + amount.Mantissa.ToString("0.###", CultureInfo.InvariantCulture) + "e" + amount.Exponent.ToString(CultureInfo.InvariantCulture); } internal void ClearStatsWagers() { statsWagers.Clear(); } private void TickDailyNegatives() { //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Invalid comparison between Unknown and I4 float now = Time.time; double elapsed = Math.Max(0f, now - lastNegativeTime); lastNegativeTime = now; if (!NetworkServer.active || !compatible || !inDay || daySuspended || sessionResetActive || engine == null || !string.IsNullOrEmpty(holdReason)) { return; } GameManager instance = NetworkSingleton.Instance; if ((Object)(object)instance == (Object)null || (int)instance.state != 1 || !instance.HasDayStarted || Time.timeScale <= 0f || NativeTransition == null || (bool)NativeTransition.GetValue(instance) || network == null || !network.ReadyForChaos) { return; } Safe(delegate { bool flag = false; foreach (NegativeProgress negative in engine.State.Negatives) { if (negative.Timed && !negative.Completed && !negative.Failed) { flag = true; break; } } if (flag) { List list = engine.TickNegatives(elapsed); if (list.Count > 0) { Apply(list); Changed(); } } if (engine.State.PendingPenaltyRewards.Count > 0) { Apply(new List(engine.State.PendingPenaltyRewards)); } if (flag && now >= nextNegativePresentation) { nextNegativePresentation = now + 1f; Changed(); } }); } private static string NegativeHud(RunState state) { if (state == null || state.Negatives == null || state.Negatives.Count == 0) { return ""; } int num = 0; foreach (NegativeProgress negative in state.Negatives) { if (!negative.Completed && !negative.Failed) { num++; } } if (num != 0) { return num + ((num == 1) ? " risk task" : " risk tasks") + " · Tab for details"; } return "Risks resolved"; } private void ReceiveCompletion(CompletionNotice notice) { if (ui != null && notice != null) { string text = DisplayText(notice.Task, 128); string text2 = DisplayText(notice.Actor, 96); string text3 = DisplayText(notice.Amount, 64); if (notice.IsGunPurchase()) { ui.ShowGunPurchaseCompletion("Gun purchased", text2 + " bought a gun for 1 ticket.\nCollect it from the elevator item guy. Pickup is free."); return; } ui.ShowCompletion(notice.Penalty ? "Daily task missed" : "Bonus paid", text + "\n" + text2 + (notice.Penalty ? " · Team lost " : " · Added ") + text3 + " shared cash", notice.Penalty); } } private void PrepareRewardActor(Reward reward) { if (string.IsNullOrEmpty(reward.ActorName)) { PlayerProfile val = ResolveRewardPlayer(reward.Actor); reward.ActorName = ((!((Object)(object)val == (Object)null)) ? DisplayText(val.playerName, 96) : ((reward.Actor == 0) ? "Team" : "A starting player")); if (string.IsNullOrEmpty(reward.ActorName)) { reward.ActorName = "A starting player"; } } } private void QueuePaidNotice(Reward reward, BigNumber actual, bool penalty) { //IL_017f: Unknown result type (might be due to invalid IL or missing references) //IL_0184: 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_015d: Unknown result type (might be due to invalid IL or missing references) //IL_0162: Unknown result type (might be due to invalid IL or missing references) if (!penalty && !reward.TaskCompleted && reward.Description != "Vault cash grant" && reward.Description != "Golden Payout" && reward.Description != "House Refund" && reward.Description != "House Credit") { return; } string key = reward.Day + "/" + reward.Round + "/" + reward.Description + "/" + reward.Actor + "/" + penalty; if (!paidNotices.TryGetValue(key, out var value)) { if (paidNotices.Count >= 16) { FlushPaidNotices(); } string text = reward.Description ?? "Chaos reward"; if (!penalty && reward.TaskCompleted && !reward.NpcKill) { text = text.Replace(" complete", "") + " completed"; } PaidNotice paidNotice = new PaidNotice(); paidNotice.Task = DisplayText(text, 128); paidNotice.Actor = reward.ActorName ?? "Team"; paidNotice.Cash = BigNumber.Zero; paidNotice.Penalty = penalty; value = paidNotice; paidNotices.Add(key, value); } PaidNotice paidNotice2 = value; paidNotice2.Cash += actual; } private void FlushPaidNotices() { //IL_0049: Unknown result type (might be due to invalid IL or missing references) foreach (PaidNotice value in paidNotices.Values) { try { if (network != null) { network.BroadcastCompletion(new CompletionNotice { Task = value.Task, Actor = value.Actor, Amount = MoneyFormatter.FormatWithDollar(value.Cash, false), Penalty = value.Penalty }); } } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogWarning((object)("Completion popup could not be sent: " + ex.Message)); } } paidNotices.Clear(); } private void ApplyPenalty(Reward reward, MoneyManager money) { //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f0: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_00f8: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0103: 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_010b: 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_0123: Unknown result type (might be due to invalid IL or missing references) //IL_0124: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012b: Unknown result type (might be due to invalid IL or missing references) //IL_0130: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0161: Unknown result type (might be due to invalid IL or missing references) //IL_016e: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0186: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0196: 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_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0147: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014a: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_014d: Unknown result type (might be due to invalid IL or missing references) //IL_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01db: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_0246: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_01ee: Unknown result type (might be due to invalid IL or missing references) //IL_0207: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_0213: Unknown result type (might be due to invalid IL or missing references) //IL_0215: Unknown result type (might be due to invalid IL or missing references) //IL_0217: Unknown result type (might be due to invalid IL or missing references) //IL_0224: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0309: Unknown result type (might be due to invalid IL or missing references) //IL_030e: Unknown result type (might be due to invalid IL or missing references) //IL_0310: Unknown result type (might be due to invalid IL or missing references) //IL_0315: Unknown result type (might be due to invalid IL or missing references) //IL_031c: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) bool flag = false; foreach (Reward pendingPenaltyReward in engine.State.PendingPenaltyRewards) { if (object.ReferenceEquals(pendingPenaltyReward, reward)) { flag = true; break; } } if (!flag || !reward.CashBound || reward.Round != engine.State.Round || reward.Day != engine.State.Day || reward.Amount <= 0.0 || reward.CashMantissa <= 0.0 || double.IsNaN(reward.CashMantissa) || double.IsInfinity(reward.CashMantissa)) { throw new InvalidOperationException("Invalid current-day cash penalty."); } if ((Object)(object)money == (Object)null) { return; } BigNumber balance = money.balance; BigNumber val = default(BigNumber); ((BigNumber)(ref val))..ctor(reward.CashMantissa, reward.CashExponent); BigNumber val2 = ((val > balance) ? balance : val); BigNumber val3 = BigNumber.Zero; if (originalQuota <= BigNumber.Zero) { throw new InvalidOperationException("Cash penalty has no original target."); } BigNumber val4 = balance - (balance - val2); int num = 0; while (val4 > val && num < 3) { val2 *= 0.999999999999; val4 = balance - (balance - val2); num++; } BigNumber val5 = val4 / originalQuota; double num2 = ((BigNumber)(ref val5)).ToDouble(); bool flag2 = val4 > BigNumber.Zero && val4 <= val && !double.IsNaN(num2) && !double.IsInfinity(num2) && num2 >= 0.0 && num2 <= reward.ReservedAmount + 1E-09; if (val2 > BigNumber.Zero && flag2) { if (!money.TryChangeBalance(-val2, (PlayerProfile)null, (ChangeType)4)) { throw new InvalidOperationException("Native shared penalty was not accepted."); } val3 = balance - money.balance; if (val3 < BigNumber.Zero || money.balance < BigNumber.Zero) { throw new InvalidOperationException("Native penalty changed the shared balance incorrectly."); } } double num3; if (!(originalQuota > BigNumber.Zero)) { num3 = 0.0; } else { BigNumber val6 = val3 / originalQuota; num3 = ((BigNumber)(ref val6)).ToDouble(); } double num4 = num3; if (double.IsNaN(num4) || double.IsInfinity(num4) || num4 < 0.0) { throw new InvalidOperationException("Invalid delivered penalty amount."); } reward.CashMantissa = val3.Mantissa; reward.CashExponent = val3.Exponent; engine.ConfirmPenalty(reward.Id, num4); if (!engine.State.AppliedRewardIds.Contains(reward.Id) || engine.FindPendingPenalty(reward.Id) != null) { throw new InvalidOperationException("Cash penalty receipt was not committed."); } nativeChaosCashDay -= val3; QueuePaidNotice(reward, val3, penalty: true); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos penalty paid: " + reward.Description + " | " + MoneyFormatter.FormatWithDollar(val3, false) + " | shared team cash | receipt " + reward.Id)); Changed(); } private static string DisplayText(string value, int maximumBytes) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(); int num = 0; for (int i = 0; i < value.Length; i++) { char c = value[i]; if (char.IsControl(c)) { continue; } string text; if (char.IsHighSurrogate(c)) { if (i + 1 >= value.Length || !char.IsLowSurrogate(value[i + 1])) { continue; } text = value.Substring(i, 2); i++; } else { if (char.IsLowSurrogate(c)) { continue; } text = c.ToString(); } int byteCount = Encoding.UTF8.GetByteCount(text); if (num + byteCount > maximumBytes) { break; } stringBuilder.Append(text); num += byteCount; } return stringBuilder.ToString().Trim(); } private void Awake() { //IL_004c: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Expected O, but got Unknown //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d4: Expected O, but got Unknown //IL_016c: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Expected O, but got Unknown //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01ca: Expected O, but got Unknown //IL_038e: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Expected O, but got Unknown Instance = this; chaosEnabled = ((BaseUnityPlugin)this).Config.Bind("Host", "Enabled", true, "Enable Chaos on the next casino day. All players need this mod."); intensity = ((BaseUnityPlugin)this).Config.Bind("Host", "Intensity", 5, new ConfigDescription("Chaos intensity.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 10), new object[0])); jackpot = ((BaseUnityPlugin)this).Config.Bind("Host", "JackpotPercent", 3f, new ConfigDescription("Chance per eligible day, after meaningful casino participation.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 20f), new object[0])); bonusCap = ((BaseUnityPlugin)this).Config.Bind("Host", "BonusCap", 0.35f, new ConfigDescription("Combined cash reward cap as a fraction of the original quota.", (AcceptableValueBase)(object)new AcceptableValueRange(0.05f, 0.5f), new object[0])); closing = ((BaseUnityPlugin)this).Config.Bind("Host", "ClosingTime", true, "Permit the ONLY clock modifier: 20% shorter day with a 20% lower target."); contracts = ((BaseUnityPlugin)this).Config.Bind("Host", "Contracts", true, "Offer optional bonus goals for extra shared cash."); negatives = ((BaseUnityPlugin)this).Config.Bind("Host", "NegativeModifiers", true, "On risk days, add one penalty task per modifier. Bonus goals never carry penalties."); riskDayChance = ((BaseUnityPlugin)this).Config.Bind("Host", "RiskDayPercent", 50f, new ConfigDescription("Chance that a new casino day has cash-risk tasks. On selected days one task accompanies each positive modifier; bonus goals stay positive.", (AcceptableValueBase)(object)new AcceptableValueRange(0f, 100f), new object[0])); timedNegatives = ((BaseUnityPlugin)this).Config.Bind("Host", "TimedNegativeTasks", false, "Allow negative tasks with a countdown. This never changes the native day's duration."); penaltySeverity = ((BaseUnityPlugin)this).Config.Bind("Host", "PenaltySeverity", 2, new ConfigDescription("1 Gentle (half), 2 Balanced (matching positive values), 3 Harsh (one-and-a-half). Penalties cannot overdraw shared cash.", (AcceptableValueBase)(object)new AcceptableValueRange(1, 3), new object[0])); seed = ((BaseUnityPlugin)this).Config.Bind("Host", "DirectorSeed", 0, "0 derives Chaos seed from the native save; other values use your chosen seed."); excluded = ((BaseUnityPlugin)this).Config.Bind("Host", "DisabledModifiers", "", "Comma-separated modifier IDs excluded from selection."); reducedMotion = ((BaseUnityPlugin)this).Config.Bind("Local", "ReducedMotion", false, "Disable decorative UI fades on this computer."); showHud = ((BaseUnityPlugin)this).Config.Bind("Local", "ShowHud", true, "Show the compact left-side Chaos HUD on this computer. F9 toggles it and remembers your choice; this does not change anyone else's display."); engine = new Engine(ReadRules()); saves = new ChaosSave(Paths.ConfigPath); ui = new ChaosUi(OnAction, OnSetting, OnFreshRun); ui.Initialize(((Component)this).transform); ui.SetHudVisible(showHud.Value); compatible = VerifyGame(); if (!compatible) { holdReason = "This game build has not been verified. Chaos gameplay is held."; Present(); return; } network = new ChaosNetwork(ReceiveSnapshot, MakeSnapshot, delegate(string text) { ((BaseUnityPlugin)this).Logger.LogInfo((object)text); }); network.HostVote += ReceiveVote; network.CompletionReceived += ReceiveCompletion; network.Initialize(); InitializePoliceBail(); InitializeStats(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Network serializer self-test: " + ChaosNetwork.SelfTest())); VerifySerialization(); harmony = new Harmony("co.oasistechnologies.gwyf.chaosnight"); InitializeNativeFeatures(); harmony.PatchAll(typeof(Plugin).Assembly); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Chaos Night 0.6.4 loaded; game API verified. Tab controls / F9 HUD. Timed negative tasks are host controlled."); Present(); } private bool VerifyGame() { try { if (SetQuotaMethod == null || AccessTools.Method(typeof(GameManager), "StartDay", (Type[])null, (Type[])null) == null || AccessTools.Method(typeof(GameResultsManager), "RegisterResult", (Type[])null, (Type[])null) == null) { return false; } using SHA256 sHA = SHA256.Create(); using FileStream inputStream = File.OpenRead(typeof(GameManager).Assembly.Location); string text = BitConverter.ToString(sHA.ComputeHash(inputStream)).Replace("-", "").ToLowerInvariant(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Game assembly SHA256: " + text)); return text == "f2147868930f1fc9df3489ef89339759f7eeb8a92c270bec094a4ece6dd2935b"; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Compatibility check: " + ex.Message)); return false; } } private Rules ReadRules() { Rules rules = new Rules(); rules.Enabled = chaosEnabled.Value; rules.Intensity = intensity.Value; rules.JackpotPercent = jackpot.Value; rules.ContractsEnabled = contracts.Value; rules.ClosingTimeEnabled = closing.Value; rules.MaxBonusQuotaFraction = bonusCap.Value; rules.NegativesEnabled = negatives.Value; rules.TimedNegativesEnabled = timedNegatives.Value; rules.PenaltySeverity = penaltySeverity.Value; rules.NegativeDayPercent = riskDayChance.Value; rules.Seed = seed.Value; rules.DisabledModifiers = excluded.Value; rules.PurchasesSupported = true; return rules; } private void Update() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (ui == null) { return; } if (!firstUpdate) { firstUpdate = true; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos update loop active; native input update mode: " + InputSystem.settings.updateMode)); } ui.Tick(); if (PoliceBailReceiptVisible) { ui.CloseForSessionReload(); } TickPoliceBailInput(); RefreshOverlayInput(); if (network != null) { network.Tick(); } TickStats(); if (gunReload != null) { gunReload.Tick(); } if (wasHost && !NetworkServer.active) { ResetRun(); } wasHost = NetworkServer.active; if (viewDirty && Time.unscaledTime >= nextView) { viewDirty = false; nextView = Time.unscaledTime + 0.25f; Present(); } if (!(Time.unscaledTime >= nextStatusCheck)) { return; } nextStatusCheck = Time.unscaledTime + 0.5f; TickSessionControls(); TickPoliceBail(); TickDailyNegatives(); TickNativeFeatures(); string text = ((network == null) ? "Compatibility hold" : network.Status); if (text != lastNetworkStatus) { lastNetworkStatus = text; Present(); } if (NetworkServer.active && inDay && !daySuspended) { if (network == null || (!network.ReadyForChaos && !network.WaitingForPeerHandshake)) { SuspendDay(); } else if (network.ReadyForChaos) { livePlayers.Clear(); foreach (NetworkConnectionToClient value in NetworkServer.connections.Values) { if (value != null && !((Object)(object)((NetworkConnection)value).identity == (Object)null)) { PlayerProfile component = ((Component)((NetworkConnection)value).identity).GetComponent(); if ((Object)(object)component != (Object)null && component.steamId != 0) { livePlayers.Add(component.steamId); } } } if (livePlayers.Count > 0) { ulong[] players = engine.State.Players; foreach (ulong num in players) { if (!livePlayers.Contains(num)) { engine.WaivePlayer(num); } } } } } if (NetworkServer.active && compatible && engine != null && engine.State.PendingCashRewards.Count > 0 && string.IsNullOrEmpty(holdReason) && !daySuspended && network != null && network.ReadyForChaos) { Safe(ApplyPendingCash); } if (NetworkServer.active && network != null && broadcastPending && Time.unscaledTime >= nextBroadcast) { nextBroadcast = Time.unscaledTime + 0.5f; broadcastPending = false; network.Broadcast(); } } private void RefreshOverlayInput() { //IL_00d4: Unknown result type (might be due to invalid IL or missing references) //IL_00da: Invalid comparison between Unknown and I4 //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_003c: 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_0100: Invalid comparison between Unknown and I4 //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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) bool flag = (ui != null && ui.WantsWorldInputBlocked) || PoliceBailBlocksInput; if (flag && !uiWasOpen) { if (InputEvents.OnMoveEvent != null) { InputEvents.OnMoveEvent(Vector2.zero); } if (InputEvents.OnAimEvent != null) { InputEvents.OnAimEvent(Vector2.zero); } if (InputEvents.IsInteractPressed) { InputEvents.UpdateInteract(false); } if (InputEvents.IsUseItemPressed) { InputEvents.UpdateUseItem(false); } if (InputEvents.IsThrowItemPressed) { InputEvents.UpdateThrowItem(false); } if (InputEvents.IsZoomPressed) { InputEvents.UpdateZoom(false); } if (InputEvents.IsJumpPressed) { InputEvents.UpdateJump(false); } if (InputEvents.IsCrouchPressed) { InputEvents.UpdateCrouch(false); } if (InputEvents.IsSprintPressed) { InputEvents.UpdateSprint(false); } if (InputEvents.IsEmoteWheelPressed) { InputEvents.UpdateEmoteWheel(false); } previousLayer = InputEvents.ActiveLayer; } else if (flag && (int)InputEvents.ActiveLayer != 3) { previousLayer = InputEvents.ActiveLayer; } if (flag) { InputEvents.ActiveLayer = (InputLayer)3; } else if (uiWasOpen && (int)InputEvents.ActiveLayer == 3) { InputEvents.ActiveLayer = previousLayer; } uiWasOpen = flag; } private void LateUpdate() { RefreshOverlayInput(); if (ui != null && ui.IsOpen) { ui.MaintainCursorLease(); } if (gunReload != null) { gunReload.LateTick(); } } internal bool MaintainOverlayCursor() { if (ui == null || !ui.IsOpen) { return false; } RefreshOverlayInput(); ui.MaintainCursorLease(); return true; } internal void Begin(GameManager manager) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 if (!NetworkServer.active || !compatible || inDay || (int)manager.state != 1) { return; } Safe(delegate { //IL_0282: Unknown result type (might be due to invalid IL or missing references) //IL_0287: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0251: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) //IL_036f: Unknown result type (might be due to invalid IL or missing references) //IL_0379: Expected I4, but got Unknown //IL_037f: Unknown result type (might be due to invalid IL or missing references) //IL_0389: Expected I4, but got Unknown //IL_0490: Unknown result type (might be due to invalid IL or missing references) //IL_04aa: Unknown result type (might be due to invalid IL or missing references) //IL_04af: Unknown result type (might be due to invalid IL or missing references) //IL_04c0: Unknown result type (might be due to invalid IL or missing references) LoadCheckpoint(); int nativeDay = manager.daysPassed + 1; bool flag = engine.State.DayOpen && !engine.State.DayEnded && engine.State.Day == manager.daysPassed + 1; if (!flag) { RestoreNativeTarget(manager); } if (!flag) { CaptureRestartBaseline(manager); } if ((flag || IsSkippedNativeDay(skippedNativeDay, manager.daysPassed)) && nativeBaselineLoaded && (Object)(object)NetworkSingleton.Instance != (Object)null) { NetworkSingleton.Instance.Network_dayStartBalanceM = nativeStartBalance.Mantissa; NetworkSingleton.Instance.Network_dayStartBalanceE = nativeStartBalance.Exponent; NetworkSingleton.Instance.NetworkdayStartTicketBalance = nativeStartTickets; } if (IsSkippedNativeDay(skippedNativeDay, manager.daysPassed)) { daySuspended = true; Present(); } else { daySuspended = flag && daySuspended; if (daySuspended || (!flag && !chaosEnabled.Value) || !string.IsNullOrEmpty(holdReason) || network == null || !network.ReadyForChaos) { HoldNativeDay(manager, nativeDay, holdReason ?? (daySuspended ? "This saved day was already suspended." : ((!chaosEnabled.Value) ? "Disabled for this day." : ((network == null) ? "Waiting for network." : network.Status)))); } else { skippedNativeDay = -1; if (!flag) { originalQuota = manager.currentQuota; originalDuration = GetDuration(manager); nativeChaosCashDay = BigNumber.Zero; } if (originalQuota <= BigNumber.Zero) { holdReason = "Native quota is unavailable."; HoldNativeDay(manager, nativeDay, holdReason); } else { List list = new List(); PlayerProfile[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (PlayerProfile val in array) { if (val.hasSynced && val.steamId != 0) { rewardPlayers[val.steamId] = val; if (!list.Contains(val.steamId)) { list.Add(val.steamId); } } } List list2 = new List(); GameBase[] array2 = Object.FindObjectsByType((FindObjectsInactive)1, (FindObjectsSortMode)0); foreach (GameBase val2 in array2) { if (IsNativeUnlockedGameFloor(val2.casinoLevel, manager.currentFloor, manager.EndlessMode) && !list2.Contains((int)val2.GameType)) { list2.Add((int)val2.GameType); } } if (list.Count == 0) { HoldNativeDay(manager, nativeDay, "Waiting for native player profiles; Chaos rechecks next day."); } else { Rules rules = ReadRules(); rules.ClosingTimeEnabled = rules.ClosingTimeEnabled && manager.RoundTimerEnabled; engine.SetRules(rules); ApplyPendingCash(); if (!flag) { dayStartTickets = (((Object)(object)NetworkSingleton.Instance == (Object)null) ? 1 : Math.Max(1L, NetworkSingleton.Instance.ticketBalance + stagedShopSpend)); } inDay = true; lastNegativeTime = Time.time; wagers.Clear(); SaveData saveData = GetSaveData(); if (flag) { if (!daySuspended) { adjustedQuota = originalQuota * engine.State.QuotaFactor; SetQuota(manager, adjustedQuota); quotaChanged = engine.State.QuotaFactor != 1.0; if (manager.RoundTimerEnabled && engine.State.ClosingTimeFactor < 1.0) { manager.Network_dayDuration = originalDuration * 0.8f; } } } else { preDayEngineJson = ChaosJson.Write(engine.State); preDayShopSpend = stagedShopSpend; Apply(engine.BeginDay(manager.daysPassed + 1, manager.currentFloor, list.ToArray(), list2.ToArray(), saveData?.seed ?? 0, originalDuration)); } if (stagedShopSpend > 0) { engine.RecordPurchase((double)stagedShopSpend / (double)dayStartTickets); } stagedShopSpend = 0L; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos day started: " + engine.BuildSummary())); Changed(); if (allIn != null) { allIn.RefreshLabels(); } } } } } }); } internal void PaidWager(BigNumber amount, PlayerProfile player, ChangeType kind, bool success) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_00be: Expected I4, but got Unknown //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_00f7: Unknown result type (might be due to invalid IL or missing references) ObserveStatsCharge(amount, player, kind, success); if (!NetworkServer.active || !inDay || daySuspended || !success || (int)kind != 0 || amount >= BigNumber.Zero || (Object)(object)player == (Object)null || (Object)(object)BetContext == (Object)null) { return; } if (network == null || !network.ReadyForChaos) { SuspendDay(); return; } if (!wagers.TryGetValue(player.steamId, out var value)) { value = new Queue(); wagers[player.steamId] = value; } if (value.Count >= 32) { value.Dequeue(); } value.Enqueue(new WagerStamp { Game = (int)BetContext.GameType, Mask = engine.GetEffectsMask(), Featured = engine.GetFeaturedGame(), SourceId = ((Object)BetContext).GetInstanceID(), Bet = -amount }); } internal void CancelGameWagers(GameBase game) { CancelStatsWagers(game); if (!NetworkServer.active || (Object)(object)game == (Object)null) { return; } int instanceID = ((Object)game).GetInstanceID(); foreach (Queue value in wagers.Values) { RemoveCancelledWagers(value, instanceID); } } private static void RemoveCancelledWagers(Queue queue, int sourceId) { int count = queue.Count; for (int i = 0; i < count; i++) { WagerStamp wagerStamp = queue.Dequeue(); if (wagerStamp.SourceId != sourceId) { queue.Enqueue(wagerStamp); } } } internal void Result(BigNumber bet, BigNumber payout, PlayerProfile player, CasinoGameType game) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: 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) //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) ObserveStatsResult(bet, payout, player, game); if (!NetworkServer.active || !inDay || daySuspended || (Object)(object)player == (Object)null || bet <= BigNumber.Zero) { return; } if (network == null || !network.ReadyForChaos) { SuspendDay(); return; } Safe(delegate { //IL_01d9: Unknown result type (might be due to invalid IL or missing references) //IL_01e3: Expected I4, but got Unknown //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0204: Unknown result type (might be due to invalid IL or missing references) //IL_022d: Unknown result type (might be due to invalid IL or missing references) //IL_0238: Unknown result type (might be due to invalid IL or missing references) //IL_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: 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) //IL_005b: 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: Invalid comparison between I4 and Unknown //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Invalid comparison between I4 and Unknown //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b6: 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_00c0: 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_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: 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) WagerStamp wagerStamp = null; if (wagers.TryGetValue(player.steamId, out var value)) { int count = value.Count; for (int i = 0; i < count; i++) { WagerStamp wagerStamp2 = value.Dequeue(); if (wagerStamp == null && (wagerStamp2.Game == -1 || wagerStamp2.Game == (int)game) && wagerStamp2.Bet == bet) { wagerStamp = wagerStamp2; } else { value.Enqueue(wagerStamp2); } } if (wagerStamp == null) { BigNumber val = BigNumber.Zero; List list = new List(); foreach (WagerStamp item2 in value) { if (item2.Game == (int)game) { val += item2.Bet; list.Add(item2); if (val >= bet) { break; } } } if (val == bet && list.Count > 0) { wagerStamp = list[0]; wagerStamp.Bet = bet; foreach (WagerStamp item3 in list) { wagerStamp.Mask &= item3.Mask; } count = value.Count; for (int j = 0; j < count; j++) { WagerStamp item = value.Dequeue(); if (!list.Contains(item)) { value.Enqueue(item); } } } } } if (wagerStamp != null) { ResultInput input = new ResultInput { Sequence = engine.State.LastSequence + 1, Player = player.steamId, Game = (int)game, BetRatio = Ratio(bet), PayoutRatio = Ratio(payout), BalanceRatio = (((Object)(object)NetworkSingleton.Instance == (Object)null) ? 0.0 : Ratio(NetworkSingleton.Instance.balance - nativeChaosCashDay)), EffectsMask = wagerStamp.Mask, FeaturedGameAtStart = wagerStamp.Featured }; long timestamp = Stopwatch.GetTimestamp(); Apply(engine.Settle(input)); eventTicks += Stopwatch.GetTimestamp() - timestamp; eventCount++; Changed(); } }); } private double Ratio(BigNumber amount) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_001c: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) if (originalQuota <= BigNumber.Zero) { return 0.0; } BigNumber val = amount / originalQuota; double num = ((BigNumber)(ref val)).ToDouble(); if (double.IsNaN(num)) { return 0.0; } return Math.Max(0.0, Math.Min(1000000.0, num)); } internal void End(GameManager manager) { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 if (!NetworkServer.active || !inDay || (int)manager.state != 1) { return; } Safe(delegate { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) if (network == null || !network.ReadyForChaos) { SuspendDay(); } if (!daySuspended) { Apply(engine.ResolveNegativesAtDayEnd()); } bool quotaMet = (Object)(object)NetworkSingleton.Instance != (Object)null && NetworkSingleton.Instance.balance >= manager.currentQuota; if (!daySuspended) { Apply(engine.EndDay(quotaMet)); } else { engine.State.DayOpen = false; engine.State.DayEnded = true; } inDay = false; wagers.Clear(); RestoreDuration(manager); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos day completed: " + engine.BuildSummary())); if (eventCount > 0) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos result processing average " + ((double)eventTicks * 1000.0 / (double)Stopwatch.Frequency / (double)eventCount).ToString("F4") + " ms; " + eventCount + " events. This is not an FPS benchmark.")); } Changed(); }); } internal void EnterMerchant() { merchantDepth++; } internal void LeaveMerchant() { merchantDepth = Math.Max(0, merchantDepth - 1); } internal void TraceTimerReturn(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)("Timer return: " + message)); } internal void TicketSpent(long amount, bool success) { if (!NetworkServer.active || !success || merchantDepth == 0 || amount >= 0 || amount == long.MinValue) { return; } if (inDay) { Safe(delegate { engine.RecordPurchase((double)(-amount) / (double)dayStartTickets); Changed(); }); } else { stagedShopSpend = Math.Min(1000000L, stagedShopSpend + Math.Min(1000000L, -amount)); } } internal void SaveLoaded() { if (NetworkServer.active && !sessionResetActive) { Safe(LoadCheckpoint); } } internal void NativeSaveReset() { if (NetworkServer.active) { NewStatsRun(); ResetRun(); Changed(); } } internal void RetryDay(GameManager manager) { if (!NetworkServer.active) { return; } Safe(delegate { //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) RestoreNativeTarget(manager); RestoreDuration(manager); if (HasMatchingRetrySnapshot(engine.State.Day, manager.daysPassed, preDayEngineJson)) { int num = ((engine.State.BetItAllConsumedDay == engine.State.Day) ? engine.State.Day : (-1)); engine.Restore(ChaosJson.Read(preDayEngineJson)); if (num >= 0) { engine.PreserveBetItAllUseForDay(num); } stagedShopSpend = preDayShopSpend; } skippedNativeDay = -1; inDay = false; daySuspended = false; nativeChaosCashDay = BigNumber.Zero; wagers.Clear(); npcAttemptEpoch = System.Guid.NewGuid().ToString("N"); if (npcCombat != null) { npcCombat.ResetAuthoritativeDeaths(); } Changed(); }); } private void Apply(List rewards) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) //IL_00a5: Unknown result type (might be due to invalid IL or missing references) //IL_04fb: Unknown result type (might be due to invalid IL or missing references) //IL_0522: Unknown result type (might be due to invalid IL or missing references) //IL_0527: Unknown result type (might be due to invalid IL or missing references) //IL_052e: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_028f: Unknown result type (might be due to invalid IL or missing references) //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_0298: Unknown result type (might be due to invalid IL or missing references) //IL_029a: Unknown result type (might be due to invalid IL or missing references) //IL_02a9: Unknown result type (might be due to invalid IL or missing references) //IL_02ab: Unknown result type (might be due to invalid IL or missing references) //IL_02ad: Unknown result type (might be due to invalid IL or missing references) //IL_02b2: Unknown result type (might be due to invalid IL or missing references) //IL_02be: Unknown result type (might be due to invalid IL or missing references) //IL_02c0: Unknown result type (might be due to invalid IL or missing references) //IL_02c2: Unknown result type (might be due to invalid IL or missing references) //IL_02c7: Unknown result type (might be due to invalid IL or missing references) //IL_02c9: Unknown result type (might be due to invalid IL or missing references) //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d0: Unknown result type (might be due to invalid IL or missing references) //IL_02d2: Unknown result type (might be due to invalid IL or missing references) //IL_02d4: Unknown result type (might be due to invalid IL or missing references) //IL_02d9: Unknown result type (might be due to invalid IL or missing references) //IL_0343: Unknown result type (might be due to invalid IL or missing references) //IL_0355: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_03b7: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) //IL_03be: Unknown result type (might be due to invalid IL or missing references) //IL_03c3: Unknown result type (might be due to invalid IL or missing references) //IL_03c7: Unknown result type (might be due to invalid IL or missing references) //IL_03cc: Unknown result type (might be due to invalid IL or missing references) //IL_03ce: Unknown result type (might be due to invalid IL or missing references) //IL_03d3: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_041f: Unknown result type (might be due to invalid IL or missing references) //IL_0462: Unknown result type (might be due to invalid IL or missing references) if (rewards == null) { return; } foreach (Reward reward in rewards) { if ((reward.Kind != RewardKind.Cash && reward.Kind != RewardKind.PenaltyCash) || engine.State.AppliedRewardIds.Contains(reward.Id)) { continue; } PrepareRewardActor(reward); if (reward.CashBound) { continue; } if (reward.Round != engine.State.Round || originalQuota <= BigNumber.Zero) { throw new InvalidOperationException("Unbound cash reward has no matching original target."); } BigNumber val = originalQuota * reward.Amount; if (reward.Kind == RewardKind.PenaltyCash) { if (!engine.BindPenalty(reward.Id, val.Mantissa, val.Exponent)) { throw new InvalidOperationException("Cash penalty could not bind to its current-day record."); } } else { reward.CashMantissa = val.Mantissa; reward.CashExponent = val.Exponent; reward.CashBound = true; } } try { BigNumber val3 = default(BigNumber); foreach (Reward reward2 in rewards) { if (engine.State.AppliedRewardIds.Contains(reward2.Id)) { continue; } if (double.IsNaN(reward2.Amount) || double.IsInfinity(reward2.Amount)) { throw new InvalidOperationException("Invalid Chaos reward."); } GameManager instance = NetworkSingleton.Instance; MoneyManager instance2 = NetworkSingleton.Instance; if (reward2.Kind == RewardKind.Cash && reward2.Amount > 0.0) { bool flag = false; foreach (Reward pendingCashReward in engine.State.PendingCashRewards) { if (object.ReferenceEquals(pendingCashReward, reward2)) { flag = true; break; } } if (!flag) { throw new InvalidOperationException("Cash reward is not the director's pending record."); } PlayerProfile val2 = ResolveRewardPlayer(reward2.Player); if ((Object)(object)val2 == (Object)null || (Object)(object)instance2 == (Object)null) { continue; } ((BigNumber)(ref val3))..ctor(reward2.CashMantissa, reward2.CashExponent); if (!engine.State.RewardIds.Contains(reward2.Id) || double.IsNaN(reward2.CashMantissa) || double.IsInfinity(reward2.CashMantissa) || reward2.CashMantissa <= 0.0) { throw new InvalidOperationException("Cash receipt cannot be committed."); } BigNumber balance = instance2.balance; if (val3 > BigNumber.MaxValue - balance || balance + val3 == balance) { continue; } BigNumber val4 = balance + val3 - balance; BigNumber val5 = val4 / val3; double num = ((BigNumber)(ref val5)).ToDouble(); if (double.IsNaN(num) || double.IsInfinity(num) || num <= 0.0) { throw new InvalidOperationException("Cash delivery cannot be represented by the native balance."); } reward2.CashMantissa = val4.Mantissa; reward2.CashExponent = val4.Exponent; reward2.Amount *= num; string preparedRetry = PrepareEarlierDayPayment(reward2, instance); string text = PrepareEarlierDayNativeRestart(reward2, val4, instance); if (!reward2.CashBound || val3 <= BigNumber.Zero || !instance2.TryChangeBalance(val3, val2, (ChangeType)1)) { throw new InvalidOperationException("Native cash reward was not accepted."); } engine.MarkRewardApplied(reward2.Id); if (!engine.State.AppliedRewardIds.Contains(reward2.Id)) { throw new InvalidOperationException("Cash receipt was not committed."); } BigNumber val6 = instance2.balance - balance; nativeChaosCashDay += val6; PreserveEarlierDayPayment(reward2, val6, instance, instance2, preparedRetry); if (text != null) { dayStartNativeJson = text; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos cash paid: " + reward2.Description + " | " + MoneyFormatter.FormatWithDollar(val6, false) + " | player " + val2.steamId + " | receipt " + reward2.Id)); QueuePaidNotice(reward2, val6, penalty: false); } else { if (reward2.Kind == RewardKind.PenaltyCash && reward2.Amount > 0.0) { ApplyPenalty(reward2, instance2); continue; } if (reward2.Kind == RewardKind.Tickets && reward2.Amount > 0.0) { if ((Object)(object)instance2 == (Object)null || !instance2.TryChangeTicketBalance((long)Math.Min(25.0, reward2.Amount))) { throw new InvalidOperationException("Native ticket reward was not accepted."); } } else if (reward2.Kind == RewardKind.QuotaFactor && (Object)(object)instance != (Object)null) { adjustedQuota = originalQuota * Math.Max(0.5, Math.Min(1.3, reward2.Amount)); SetQuota(instance, adjustedQuota); quotaChanged = true; } else if (reward2.Kind == RewardKind.ClosingTimeFactor && (Object)(object)instance != (Object)null && instance.RoundTimerEnabled && inDay) { instance.Network_dayDuration = originalDuration * 0.8f; } } engine.MarkRewardApplied(reward2.Id); if (reward2.Kind == RewardKind.Cash) { Changed(); } } } finally { FlushPaidNotices(); } } private void ApplyPendingCash() { if (engine != null && engine.State.PendingCashRewards.Count > 0) { Apply(new List(engine.State.PendingCashRewards)); } } private PlayerProfile ResolveRewardPlayer(ulong steamId) { if (steamId == 0) { return null; } if (rewardPlayers.TryGetValue(steamId, out var value) && (Object)(object)value != (Object)null && value.steamId == steamId) { return value; } foreach (NetworkConnectionToClient value2 in NetworkServer.connections.Values) { if (value2 != null && !((Object)(object)((NetworkConnection)value2).identity == (Object)null)) { value = ((Component)((NetworkConnection)value2).identity).GetComponent(); if ((Object)(object)value != (Object)null && value.steamId == steamId) { rewardPlayers[steamId] = value; return value; } } } return null; } private string PrepareEarlierDayPayment(Reward reward, GameManager manager) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 if ((Object)(object)manager == (Object)null || (int)manager.state != 1 || reward.Day >= manager.daysPassed + 1 || string.IsNullOrEmpty(preDayEngineJson)) { return null; } Engine engine = new Engine(ReadRules()); engine.Restore(ChaosJson.Read(preDayEngineJson)); foreach (Reward pendingCashReward in engine.State.PendingCashRewards) { if (pendingCashReward.Id == reward.Id) { pendingCashReward.CashBound = reward.CashBound; pendingCashReward.CashMantissa = reward.CashMantissa; pendingCashReward.CashExponent = reward.CashExponent; pendingCashReward.Amount = reward.Amount; engine.MarkRewardApplied(pendingCashReward.Id); if (!engine.State.AppliedRewardIds.Contains(pendingCashReward.Id)) { throw new InvalidOperationException("Earlier-day retry receipt cannot be prepared."); } return ChaosJson.Write(engine.State); } } return null; } private void PreserveEarlierDayPayment(Reward reward, BigNumber cash, GameManager manager, MoneyManager money, string preparedRetry) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Invalid comparison between Unknown and I4 //IL_0031: 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) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)manager == (Object)null) && (int)manager.state == 1 && reward.Day < manager.daysPassed + 1) { if (preparedRetry != null) { preDayEngineJson = preparedRetry; } BigNumber val = money.dayStartBalance + cash; money.Network_dayStartBalanceM = val.Mantissa; money.Network_dayStartBalanceE = val.Exponent; } } private static void SetQuota(GameManager manager, BigNumber value) { //IL_000f: Unknown result type (might be due to invalid IL or missing references) SetQuotaMethod.Invoke(manager, new object[1] { value }); } private static float GetDuration(GameManager manager) { return (float)GetDurationMethod.Invoke(manager, null); } private static SaveData GetSaveData() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Expected O, but got Unknown if (!((Object)(object)NetworkSingleton.Instance == (Object)null)) { return (SaveData)SaveDataField.GetValue(NetworkSingleton.Instance); } return null; } internal void RestoreNativeTarget(GameManager manager) { //IL_0012: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) if (quotaChanged && (Object)(object)manager != (Object)null && manager.currentQuota == adjustedQuota) { SetQuota(manager, originalQuota); } quotaChanged = false; } private void RestoreDuration(GameManager manager) { if ((Object)(object)manager != (Object)null && engine != null && engine.State.ClosingTimeFactor < 1.0 && IsOwnedClosingDuration(GetDuration(manager), originalDuration)) { manager.Network_dayDuration = originalDuration; } } private static bool IsOwnedClosingDuration(float current, float original) { if (original <= 0f || float.IsNaN(original) || float.IsInfinity(original) || float.IsNaN(current) || float.IsInfinity(current)) { return false; } double num = (double)original * 0.800000011920929; return Math.Abs((double)current - num) <= Math.Max(1E-05, Math.Abs(num) * 5E-07); } private static bool IsSkippedNativeDay(int skipped, int daysPassed) { if (skipped >= 0) { return skipped == (long)daysPassed + 1L; } return false; } private static bool HasMatchingRetrySnapshot(int engineDay, int daysPassed, string beforeDay) { if (!string.IsNullOrEmpty(beforeDay)) { return engineDay == (long)daysPassed + 1L; } return false; } private void HoldNativeDay(GameManager manager, int nativeDay, string reason) { skippedNativeDay = nativeDay; daySuspended = true; inDay = false; wagers.Clear(); RestoreDuration(manager); RestoreNativeTarget(manager); if (engine.State.DayOpen) { engine.State.DayOpen = false; engine.State.DayEnded = true; } ((BaseUnityPlugin)this).Logger.LogInfo((object)("Chaos held for native day " + nativeDay + ": " + reason)); Changed(); SaveCheckpoint(GetSaveData()); Present(); } private void OnSetting(string key, float value) { if (key == "hudvisible") { showHud.Value = value > 0.5f; if (ui != null) { ui.SetHudVisible(showHud.Value); } } else if (key == "reducedmotion") { reducedMotion.Value = value > 0.5f; Present(); } else if (!NetworkClient.active || NetworkServer.active) { switch (key) { case "enabled": chaosEnabled.Value = value > 0.5f; break; case "intensity": intensity.Value = Mathf.Clamp(Mathf.RoundToInt(value), 1, 10); break; case "jackpot": jackpot.Value = Mathf.Clamp(value, 0f, 20f); break; case "bonuscap": bonusCap.Value = Mathf.Clamp(value / 100f, 0.05f, 0.5f); break; case "closingtime": closing.Value = value > 0.5f; break; case "contracts": contracts.Value = value > 0.5f; break; case "negatives": negatives.Value = value > 0.5f; break; case "riskChance": riskDayChance.Value = Mathf.Clamp(value, 0f, 100f); break; case "timednegatives": timedNegatives.Value = value > 0.5f; break; case "penaltyseverity": penaltySeverity.Value = Mathf.Clamp(Mathf.RoundToInt(value), 1, 3); break; case "preset": { int num = Mathf.RoundToInt(value); intensity.Value = num switch { 2 => 9, 1 => 7, _ => 5, }; jackpot.Value = num switch { 2 => 2, 1 => 10, _ => 3, }; bonusCap.Value = num switch { 2 => 0.2f, 1 => 0.45f, _ => 0.35f, }; break; } } Changed(); } } private void OnAction(int action, int index) { if (action == 6) { CastLocalVote(index == 1); } else if (action == 5) { RequestRestartVote(); } else if (action == 7) { CancelRestartVote(); } else if (action == 8) { RepairAllPlayerBodies(); } else { if (!NetworkServer.active || !compatible || daySuspended || !string.IsNullOrEmpty(holdReason)) { return; } if (network == null || !network.ReadyForChaos) { SuspendDay(); return; } Safe(delegate { if (action == 1) { Apply(engine.Choose(index)); } else if (action == 2 && inDay && !HasPendingWagers) { Apply(engine.RerollModifier()); } else if (action == 3 && inDay && !HasPendingWagers) { Apply(engine.RerollContract()); } else if (action == 4) { Apply(engine.RerollPerks()); } Changed(); }); } } private UiState BuildView() { //IL_025f: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) if (NetworkClient.active && !NetworkServer.active && guestView != null) { guestView.IsHost = false; guestView.ReducedMotion = reducedMotion.Value; guestView.Status = ((network == null) ? "Waiting for Chaos host" : network.Status); guestView.CanRepairAllBodies = false; guestView.BodyRepairHint = "Ask the host to repair everyone. Free; no reload."; if (network == null || !network.ReadyForChaos) { guestView.GameplayActive = false; guestView.Modifiers = "Paused. Everyone needs the same Chaos Night version."; guestView.AllInAvailable = false; guestView.AllInEnabled = false; guestView.HudModifiers = "Bonuses paused"; guestView.Contract = ""; guestView.ActionHint = ""; guestView.NegativeModifiers = "Paused. No timer or new cash loss while waiting."; guestView.RiskLimits = ""; guestView.HudNegatives = ""; guestView.HudRisk = ""; guestView.HudGoal = ""; guestView.HudRiskActive = false; guestView.ModifierProgress = new UiProgress[0]; guestView.RiskProgress = new UiProgress[0]; guestView.GoalProgress = null; guestView.CashTargetProgress = null; guestView.BonusCapProgress = null; guestView.RewardSummary = "No new rewards while bonuses are on hold."; guestView.Choices = new string[0]; guestView.CanVote = false; guestView.VoteVisible = false; } else { guestView.CanVote = CanPlayerVote(guestView, LocalPlayerId()); } return guestView; } RunState runState = ((engine == null) ? null : engine.State); bool flag = inDay && !daySuspended && string.IsNullOrEmpty(holdReason); Func cash = FormatQuotaCash; Func game = StatsGameName; Func tickets = (double ratio) => ChaosPresentation.FormatTicketAmount(dayStartTickets, ratio); double balance = ((flag && originalQuota > BigNumber.Zero && (Object)(object)NetworkSingleton.Instance != (Object)null) ? Ratio(NetworkSingleton.Instance.balance) : double.NaN); string text = RerollHint(2); string text2 = RerollHint(3); string text3 = RerollHint(4); string status = holdReason ?? (daySuspended ? "Paused until next day. Check everyone's mod version." : (flag ? (((runState.Players.Length == 1) ? "Solo" : (runState.Players.Length + " players at day start")) + " · Bonuses active") : ((!chaosEnabled.Value) ? "Bonuses off next day." : "Ready for the next casino day."))); UiState uiState = new UiState(); uiState.IsHost = !NetworkClient.active || NetworkServer.active; uiState.Enabled = chaosEnabled.Value; uiState.GameplayActive = flag; uiState.Status = status; uiState.AllInEnabled = flag && runState != null && runState.DayEnabled && runState.ModifierIds.Contains("bet_it_all"); uiState.AllInAvailable = flag && engine != null && engine.CanBetItAll(); uiState.Round = ((runState == null || runState.Day < 1) ? "Next casino day" : ("Day " + runState.Day)); uiState.HudModifiers = ((!flag) ? "Bonuses inactive" : ChaosPresentation.HudModifiers(runState, cash, game, tickets)); uiState.HudRisk = (flag ? ChaosPresentation.HudRisk(runState, cash) : ""); uiState.HudGoal = (flag ? ChaosPresentation.HudGoal(runState, cash, game, tickets) : ""); uiState.HudRiskActive = flag && ChaosPresentation.HasUnresolvedRisk(runState); uiState.HudNegatives = (flag ? NegativeHud(runState) : ""); uiState.Modifiers = (flag ? ChaosPresentation.Modifiers(runState, cash, game, tickets) : "Today's modifiers appear when you enter the casino."); uiState.NegativeModifiers = (flag ? ChaosPresentation.Negatives(runState, cash, game) : "Risk tasks appear on some casino days."); uiState.RiskLimits = (flag ? ChaosPresentation.RiskLimits(runState, cash) : ""); uiState.Contract = (flag ? ChaosPresentation.Contract(runState, cash, game, tickets) : "No active bonus goal."); uiState.ActionHint = (flag ? ChaosPresentation.ActionHint(runState, cash, game, tickets) : ""); uiState.Perks = ((runState == null) ? "No team bonuses yet." : ChaosPresentation.Perks(runState, cash, game, tickets)); uiState.Summary = ((runState == null) ? "Finish a casino day to start your run history." : ChaosPresentation.RunHistory(runState, cash)); uiState.Choices = ((runState == null || daySuspended) ? new string[0] : ChaosPresentation.Choices(runState, cash, game, tickets)); uiState.CashSummary = CashSummary(); uiState.RewardSummary = RewardSummary(); uiState.ModifierProgress = (flag ? ChaosProgressPresentation.Modifiers(runState, balance, cash) : new UiProgress[0]); uiState.RiskProgress = (flag ? ChaosProgressPresentation.Risks(runState, cash) : new UiProgress[0]); uiState.GoalProgress = (flag ? ChaosProgressPresentation.Goal(runState, balance, cash) : null); uiState.CashTargetProgress = (flag ? ChaosProgressPresentation.Target(runState, balance, cash) : null); uiState.BonusCapProgress = (flag ? ChaosProgressPresentation.BonusCap(runState, cash) : null); uiState.RerollModifierHint = text; uiState.RerollContractHint = text2; uiState.RerollPerksHint = text3; uiState.SettingsHint = "Daily settings apply next day. Run controls below apply immediately. All cash is shared."; uiState.Intensity = intensity.Value; uiState.JackpotPercent = jackpot.Value; uiState.BonusCap = bonusCap.Value * 100f; uiState.ClosingTimeEnabled = closing.Value; uiState.ContractsEnabled = contracts.Value; uiState.ReducedMotion = reducedMotion.Value; uiState.Preset = CurrentPreset(); uiState.NegativesEnabled = negatives.Value; uiState.TimedNegativesEnabled = timedNegatives.Value; uiState.PenaltySeverity = penaltySeverity.Value; uiState.RiskDayPercent = riskDayChance.Value; uiState.CanRerollModifier = text == "Ready. Once per chapter, before betting."; uiState.CanRerollContract = text2 == "Ready. Once per chapter, before betting."; uiState.CanRerollPerks = text3 == "Ready. Refresh once per chapter."; UiState uiState2 = uiState; AddSessionControls(uiState2); AddBodyRepairControls(uiState2); AddPoliceBailView(uiState2); return uiState2; } private string FormatQuotaCash(double ratio) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) if (!(originalQuota <= BigNumber.Zero)) { return MoneyFormatter.FormatWithDollar(originalQuota * ratio, false); } return "cash shown when the day starts"; } private string CashSummary() { //IL_0009: 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) //IL_0087: Unknown result type (might be due to invalid IL or missing references) if (engine == null || originalQuota <= BigNumber.Zero) { return "Cash totals appear when you enter the casino."; } RunState state = engine.State; double ratio = ((state.ActiveRules == null) ? ((double)bonusCap.Value) : state.ActiveRules.MaxBonusQuotaFraction); return "Today's target: " + FormatQuotaCash(state.QuotaFactor) + (state.LegacyCash ? "\nEarlier bonuses: " : "\nChaos cash change: ") + MoneyFormatter.FormatWithDollar(nativeChaosCashDay, false) + "\nDaily casino-bonus cap: " + FormatQuotaCash(ratio) + "\nRisk penalties paid: " + FormatQuotaCash(state.PenaltyDelivered) + "\nNPC cash earned: " + FormatQuotaCash(state.NpcCashDelivered) + " (1% of today's target per kill)\nCasino winnings and NPC cash are outside the bonus cap."; } private string RewardSummary() { //IL_00b9: Unknown result type (might be due to invalid IL or missing references) //IL_0188: Unknown result type (might be due to invalid IL or missing references) //IL_0227: Unknown result type (might be due to invalid IL or missing references) if (engine == null) { return "No Chaos payments yet."; } RunState state = engine.State; List list = new List(); if (state.LegacyCash) { list.Add("Older bonuses aren't itemized here and won't be paid twice."); } int num = Math.Max(0, state.CashReceipts.Count - 6); for (int i = num; i < state.CashReceipts.Count; i++) { Reward reward = state.CashReceipts[i]; PlayerProfile val = ResolveRewardPlayer(reward.Player); string text = (((Object)(object)val == (Object)null) ? ("Player " + reward.Player) : val.playerName); list.Add("Paid " + MoneyFormatter.FormatWithDollar(new BigNumber(reward.CashMantissa, reward.CashExponent), false) + " · " + reward.Description.Replace(" complete", "") + " · " + text + " (shared cash)"); } foreach (Reward pendingCashReward in state.PendingCashRewards) { if (list.Count >= 10) { list.Add("More payments waiting."); break; } PlayerProfile val2 = ResolveRewardPlayer(pendingCashReward.Player); string text2 = (pendingCashReward.CashBound ? MoneyFormatter.FormatWithDollar(new BigNumber(pendingCashReward.CashMantissa, pendingCashReward.CashExponent), false) : FormatQuotaCash(pendingCashReward.Amount)); list.Add("Waiting: " + text2 + " · " + (((Object)(object)val2 == (Object)null) ? "Player must reconnect." : "Waiting for room in the shared bankroll.")); } for (int j = Math.Max(0, state.PenaltyReceipts.Count - 4); j < state.PenaltyReceipts.Count; j++) { Reward reward2 = state.PenaltyReceipts[j]; list.Add("Team lost " + MoneyFormatter.FormatWithDollar(new BigNumber(reward2.CashMantissa, reward2.CashExponent), false) + " · " + reward2.Description + " (shared cash)"); } if (state.ContractCompleted && state.CashAwarded >= ((state.ActiveRules == null) ? 0.35 : state.ActiveRules.MaxBonusQuotaFraction) - 1E-12) { list.Add("Bonus cap reached. Later goals pay no extra cash today."); } if (list.Count == 0) { list.Add("No payments yet. Earned cash appears in the top-right bankroll."); } return string.Join("\n", list.ToArray()); } private string RerollHint(int action) { if (NetworkClient.active && !NetworkServer.active) { return "Only the host can use this."; } if (engine == null || !compatible || daySuspended || !string.IsNullOrEmpty(holdReason) || network == null || !network.ReadyForChaos) { return "Waiting for an active day and matching mod versions."; } RunState state = engine.State; string item = action switch { 3 => "second_opinion", 2 => "wildcard", _ => "second_draw", }; string text = action switch { 3 => "Second Opinion", 2 => "Wildcard", _ => "Second Draw", }; if (!state.PerkIds.Contains(item)) { return "Requires " + text + "."; } if (!state.DayEnabled || state.ModifierIds.Contains("plain_luck")) { return "Perks are paused today."; } if (action == 4) { if (state.SecondDrawChapter == state.Chapter) { return "Used this chapter (" + state.ActiveRules.ChapterRounds + " days)."; } if (state.Choices.Count == 0 || state.Choices[0].Kind != "perk") { return "Open a perk choice first."; } return "Ready. Refresh once per chapter."; } if (!inDay || !state.DayOpen) { return "Use when a casino day starts, before betting."; } if (state.QualifyingPlays > 0 || HasPendingWagers) { return "Betting started. Try next day."; } if (((action == 2) ? state.WildcardChapter : state.SecondOpinionChapter) == state.Chapter) { return "Used this chapter (" + state.ActiveRules.ChapterRounds + " days)."; } if (action == 2 && state.ModifierIds.Contains("closing_time")) { return "The shorter day has already started, so this rule cannot be replaced."; } if (action == 3 && string.IsNullOrEmpty(state.ContractId)) { return "There is no bonus goal to replace today."; } return "Ready. Once per chapter, before betting."; } private void Present() { UiState uiState = BuildView(); if (ui != null) { ui.Present(uiState); } if (policeBailHud != null) { policeBailHud.Present(uiState, LocalPlayerId()); } } private string CurrentPreset() { if (intensity.Value == 5 && jackpot.Value == 3f && Math.Abs(bonusCap.Value - 0.35f) < 0.0001f) { return "Standard"; } if (intensity.Value == 7 && jackpot.Value == 10f && Math.Abs(bonusCap.Value - 0.45f) < 0.0001f) { return "Party"; } if (intensity.Value == 9 && jackpot.Value == 2f && Math.Abs(bonusCap.Value - 0.2f) < 0.0001f) { return "Challenge"; } return "Custom"; } private void Changed() { revision++; viewDirty = true; broadcastPending = true; } private string MakeSnapshot() { SyncLocalNpcDeaths(); ViewEnvelope viewEnvelope = new ViewEnvelope(); viewEnvelope.Session = session; viewEnvelope.Revision = revision; viewEnvelope.View = BuildView(); viewEnvelope.NpcDayToken = CurrentNpcDayToken(); viewEnvelope.NpcKilledKeys = ((npcCombat != null) ? npcCombat.GetAuthoritativeDeathKeys() : ((engine == null) ? new string[0] : engine.State.KilledNpcKeys.ToArray())); viewEnvelope.NpcPanic = ((npcCombat == null) ? null : npcCombat.GetPanicEvent()); return ChaosJson.Write(viewEnvelope); } private void ReceiveSnapshot(string json) { if (NetworkServer.active) { return; } try { ViewEnvelope viewEnvelope = ChaosJson.Read(json); if (viewEnvelope == null || viewEnvelope.View == null || viewEnvelope.View.Choices == null || viewEnvelope.View.Choices.Length > 3 || viewEnvelope.Revision < 0 || string.IsNullOrEmpty(viewEnvelope.Session)) { throw new InvalidDataException("Invalid Chaos snapshot schema."); } ValidateVoteView(viewEnvelope.View); ValidatePoliceBailView(viewEnvelope.View); ValidateNpcSnapshot(viewEnvelope); if (viewEnvelope.View.VoteId > 0 && viewEnvelope.View.VoteEpoch != viewEnvelope.Session) { throw new InvalidDataException("Mismatched vote session."); } if (!(guestSession == viewEnvelope.Session) || viewEnvelope.Revision >= guestRevision) { guestSession = viewEnvelope.Session; guestRevision = viewEnvelope.Revision; guestNpcDayToken = viewEnvelope.NpcDayToken; if (guestView != null && guestView.PoliceBailVisible && !viewEnvelope.View.PoliceBailVisible) { policeBailClosedFrame = Time.frameCount; } guestView = viewEnvelope.View; if (npcCombat != null) { npcCombat.SetAuthoritativeDeaths(viewEnvelope.NpcDayToken, viewEnvelope.NpcKilledKeys, guestView.GameplayActive); } if (npcCombat != null) { npcCombat.SetAuthoritativePanic(guestView.GameplayActive ? viewEnvelope.NpcPanic : null); } Present(); if (allIn != null) { allIn.RefreshLabels(); } if (guestView.SessionReloading || guestView.PoliceBailVisible || Time.frameCount == policeBailClosedFrame) { RefreshOverlayInput(); } } } catch (Exception innerException) { guestView = null; guestNpcDayToken = null; throw new InvalidDataException("Rejected Chaos snapshot", innerException); } } private void VerifySerialization() { Engine engine = new Engine(ReadRules()); engine.BeginDay(1, 1, new ulong[1] { 76561198271465289uL }, new int[3] { 0, 2, 11 }, 12345); string text = ChaosJson.Write(engine.State); RunState runState = ChaosJson.Read(text); if (runState == null || runState.Players == null || runState.Players.Length != 1 || runState.Players[0] != 76561198271465289L || runState.ModifierIds == null || runState.ActiveRules == null) { throw new InvalidOperationException("Core JSON roundtrip failed: " + text); } ViewEnvelope viewEnvelope = ChaosJson.Read(MakeSnapshot()); if (viewEnvelope == null || viewEnvelope.View == null || viewEnvelope.View.Choices == null) { throw new InvalidOperationException("UI snapshot JSON roundtrip failed."); } UiState uiState = new UiState(); uiState.ActionHint = "Finish 3 casino games"; uiState.RewardSummary = "Paid $72 · Everybody In · Player"; uiState.AllInEnabled = true; uiState.AllInAvailable = true; uiState.CashSummary = "Extra cash paid today: $132 of $420"; uiState.RerollModifierHint = "Unlock Wildcard first."; uiState.RerollContractHint = "Unlock Second Opinion first."; uiState.RerollPerksHint = "Choose a team bonus first."; uiState.SettingsHint = "Changes start next day."; uiState.Choices = new string[0]; UiState uiState2 = uiState; UiState uiState3 = ChaosJson.Read(ChaosJson.Write(uiState2)); UiState uiState4 = ChaosJson.Read("{\"Choices\":[]}"); if (uiState3.ActionHint != uiState2.ActionHint || uiState3.RewardSummary != uiState2.RewardSummary || uiState3.CashSummary != uiState2.CashSummary || uiState3.SettingsHint != uiState2.SettingsHint || !uiState3.AllInEnabled || !uiState3.AllInAvailable || uiState4 == null || uiState4.AllInEnabled || uiState4.AllInAvailable || uiState3.RerollModifierHint != uiState2.RerollModifierHint || uiState3.RerollContractHint != uiState2.RerollContractHint || uiState3.RerollPerksHint != uiState2.RerollPerksHint || uiState4 == null || uiState4.ActionHint != null || Encoding.UTF8.GetByteCount(MakeSnapshot()) > 16384) { throw new InvalidOperationException("Plain menu snapshot roundtrip/fallback/bounds failed."); } if (!IsNativeUnlockedGameFloor(1, 0, endlessMode: false) || IsNativeUnlockedGameFloor(2, 0, endlessMode: false) || !IsNativeUnlockedGameFloor(3, 2, endlessMode: false) || IsNativeUnlockedGameFloor(4, 2, endlessMode: false) || !IsNativeUnlockedGameFloor(4, 10, endlessMode: true) || IsNativeUnlockedGameFloor(5, 10, endlessMode: true) || IsNativeUnlockedGameFloor(-1, 0, endlessMode: false)) { throw new InvalidOperationException("Native unlocked-game floor discovery regression failed."); } VerifyAdapterInvariants(); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Core checkpoint, UI snapshot JSON, native floor, held-day/retry, duration and cancelled-wager self-tests: PASS"); } private static void VerifyAdapterInvariants() { if (!IsOwnedClosingDuration(240f, 300f) || !IsOwnedClosingDuration(350f, 437.5f) || IsOwnedClosingDuration(240.2f, 300f) || IsOwnedClosingDuration(240f, 0f) || IsOwnedClosingDuration(float.NaN, 300f)) { throw new InvalidOperationException("Native Closing Time float-restoration regression failed."); } if (!IsSkippedNativeDay(1, 0) || IsSkippedNativeDay(1, 1) || IsSkippedNativeDay(-1, 0) || !HasMatchingRetrySnapshot(1, 0, "before") || HasMatchingRetrySnapshot(1, 1, "before") || HasMatchingRetrySnapshot(0, 0, null)) { throw new InvalidOperationException("Held native-day and retry identity regression failed."); } Queue queue = new Queue(); queue.Enqueue(new WagerStamp { SourceId = 11 }); queue.Enqueue(new WagerStamp { SourceId = 12 }); queue.Enqueue(new WagerStamp { SourceId = 11 }); RemoveCancelledWagers(queue, 11); if (queue.Count != 1 || queue.Peek().SourceId != 12) { throw new InvalidOperationException("Cancelled source-table wager removal regression failed."); } RemoveCancelledWagers(queue, 999); if (queue.Count != 1 || queue.Peek().SourceId != 12) { throw new InvalidOperationException("Other source-table wager preservation regression failed."); } } private static bool IsNativeUnlockedGameFloor(int gameFloor, int progressionFloor, bool endlessMode) { long num = Math.Max(1L, (long)progressionFloor + 1L); if (endlessMode) { num = Math.Min(num, 4L); } if (gameFloor >= 0) { return gameFloor <= num; } return false; } private string NativePath(string name) { if (string.IsNullOrEmpty(name) || Path.GetFileName(name) != name) { return null; } string path = (((Object)(object)NetworkSingleton.Instance == (Object)null) ? Path.Combine(Application.persistentDataPath, "Saves") : ((string)GetSaveDirectoryMethod.Invoke(NetworkSingleton.Instance, null))); return Path.Combine(path, name + ".json"); } private void LoadCheckpoint() { //IL_00ba: Unknown result type (might be due to invalid IL or missing references) //IL_00bf: 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_00cf: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0112: Unknown result type (might be due to invalid IL or missing references) //IL_0117: Unknown result type (might be due to invalid IL or missing references) //IL_01cb: Unknown result type (might be due to invalid IL or missing references) //IL_01d0: Unknown result type (might be due to invalid IL or missing references) SaveManager instance = NetworkSingleton.Instance; SaveData saveData = GetSaveData(); if ((Object)(object)instance == (Object)null || saveData == null) { return; } string saveName = saveData.saveName; if (checkpointLoaded && saveName == lastSave) { return; } ResetRun(); checkpointLoaded = true; lastSave = saveName; string text = NativePath(saveName); if (text != null && File.Exists(text)) { string reason; ChaosCheckpoint chaosCheckpoint = saves.Load(saveName, File.ReadAllText(text), out reason); if (reason != null) { holdReason = reason; ((BaseUnityPlugin)this).Logger.LogWarning((object)reason); } else if (chaosCheckpoint != null) { engine.Restore(ChaosJson.Read(chaosCheckpoint.EngineJson)); originalQuota = new BigNumber(chaosCheckpoint.QuotaMantissa, chaosCheckpoint.QuotaExponent); nativeChaosCashDay = (BigNumber)(chaosCheckpoint.HasCashDeliveryBasis ? new BigNumber(chaosCheckpoint.CashDeliveryMantissa, chaosCheckpoint.CashDeliveryExponent) : (originalQuota * engine.State.CashAwarded)); adjustedQuota = new BigNumber(chaosCheckpoint.AdjustedQuotaMantissa, chaosCheckpoint.AdjustedQuotaExponent); quotaChanged = chaosCheckpoint.QuotaAdjusted; originalDuration = chaosCheckpoint.OriginalDuration; preDayEngineJson = chaosCheckpoint.PreDayEngineJson; dayStartNativeJson = chaosCheckpoint.DayStartNativeJson; dayStartNativeDay = chaosCheckpoint.DayStartNativeDay; LoadPoliceBailCheckpoint(chaosCheckpoint, saveData); dayStartTickets = Math.Max(1L, chaosCheckpoint.DayStartTickets); stagedShopSpend = Math.Max(0L, chaosCheckpoint.StagedShopSpend); preDayShopSpend = Math.Max(0L, chaosCheckpoint.PreDayShopSpend); daySuspended = chaosCheckpoint.DaySuspended; skippedNativeDay = chaosCheckpoint.SkippedNativeDay; nativeStartBalance = new BigNumber(chaosCheckpoint.NativeStartBalanceMantissa, chaosCheckpoint.NativeStartBalanceExponent); nativeStartTickets = chaosCheckpoint.NativeStartTickets; nativeBaselineLoaded = chaosCheckpoint.HasNativeBaseline; revision = chaosCheckpoint.Revision + 1; ((BaseUnityPlugin)this).Logger.LogInfo((object)("Restored paired Chaos checkpoint for " + saveName)); } } } internal void SaveCheckpoint(SaveData data) { QueueStatsPersistence(); if (!NetworkServer.active || engine == null || data == null || !string.IsNullOrEmpty(holdReason)) { return; } Safe(delegate { //IL_02a0: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) string text = NativePath(data.saveName); if (text != null && File.Exists(text)) { string text2 = File.ReadAllText(text); if (!(ChaosSave.Hash(text2) != ChaosSave.Hash(JsonUtility.ToJson((object)data, true)))) { saves.Write(new ChaosCheckpoint { NativeSaveName = data.saveName, NativeJson = text2, NativeHash = ChaosSave.Hash(text2), EngineJson = ChaosJson.Write(engine.State), RulesJson = ChaosJson.Write(ReadRules()), QuotaMantissa = originalQuota.Mantissa, QuotaExponent = originalQuota.Exponent, HasCashDeliveryBasis = true, CashDeliveryMantissa = nativeChaosCashDay.Mantissa, CashDeliveryExponent = nativeChaosCashDay.Exponent, DayStartNativeJson = dayStartNativeJson, DayStartNativeDay = dayStartNativeDay, PoliceBailJson = ((policeBailState == null) ? null : ChaosJson.Write(policeBailState)), AdjustedQuotaMantissa = adjustedQuota.Mantissa, AdjustedQuotaExponent = adjustedQuota.Exponent, QuotaAdjusted = quotaChanged, OriginalDuration = originalDuration, DaySuspended = daySuspended, SkippedNativeDay = skippedNativeDay, PreDayEngineJson = preDayEngineJson, DayStartTickets = dayStartTickets, StagedShopSpend = stagedShopSpend, PreDayShopSpend = preDayShopSpend, NativeDays = data.daysPassed, NativeQuotas = data.successfulQuota, Revision = revision, HasNativeBaseline = ((Object)(object)NetworkSingleton.Instance != (Object)null && (inDay || ((Object)(object)NetworkSingleton.Instance != (Object)null && IsSkippedNativeDay(skippedNativeDay, NetworkSingleton.Instance.daysPassed)))), NativeStartBalanceMantissa = (((Object)(object)NetworkSingleton.Instance == (Object)null) ? 0.0 : NetworkSingleton.Instance.dayStartBalance.Mantissa), NativeStartBalanceExponent = (((Object)(object)NetworkSingleton.Instance == (Object)null) ? 0 : NetworkSingleton.Instance.dayStartBalance.Exponent), NativeStartTickets = (((Object)(object)NetworkSingleton.Instance == (Object)null) ? 0 : NetworkSingleton.Instance.dayStartTicketBalance) }); } } }); } private void Safe(Action action) { try { action(); } catch (Exception ex) { holdReason = "Chaos paused safely after an integration error. See BepInEx log."; ((BaseUnityPlugin)this).Logger.LogError((object)ex); inDay = false; if (NetworkServer.active && (Object)(object)NetworkSingleton.Instance != (Object)null) { RestoreDuration(NetworkSingleton.Instance); RestoreNativeTarget(NetworkSingleton.Instance); } Present(); } } private void ResetRun() { //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) ReleaseStatsRun(); restartVote = null; bodyRepairNote = null; ResetPoliceBailRun(); engine = new Engine(ReadRules()); inDay = false; daySuspended = false; quotaChanged = false; npcAttemptEpoch = System.Guid.NewGuid().ToString("N"); if (npcCombat != null) { npcCombat.ResetAuthoritativeDeaths(); } skippedNativeDay = -1; originalQuota = BigNumber.Zero; adjustedQuota = BigNumber.Zero; originalDuration = 0f; nativeChaosCashDay = BigNumber.Zero; lastNegativeTime = Time.time; nextNegativePresentation = 0f; paidNotices.Clear(); stagedShopSpend = 0L; preDayShopSpend = 0L; dayStartTickets = 1L; checkpointLoaded = false; lastSave = null; holdReason = null; wagers.Clear(); rewardPlayers.Clear(); guestView = null; guestSession = null; guestNpcDayToken = null; guestRevision = -1L; session = System.Guid.NewGuid().ToString("N"); revision++; preDayEngineJson = null; dayStartNativeJson = null; dayStartNativeDay = 0; nativeBaselineLoaded = false; nativeStartBalance = BigNumber.Zero; nativeStartTickets = 0L; } private void SuspendDay() { if (inDay && !daySuspended) { daySuspended = true; wagers.Clear(); if ((Object)(object)NetworkSingleton.Instance != (Object)null) { RestoreNativeTarget(NetworkSingleton.Instance); RestoreDuration(NetworkSingleton.Instance); } ((BaseUnityPlugin)this).Logger.LogWarning((object)"Chaos suspended until next day because lobby compatibility changed."); Changed(); } } private void OnDestroy() { //IL_003e: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Invalid comparison between Unknown and I4 //IL_0047: 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) DisposePoliceBail(); DisposeStats(); if (NetworkServer.active && (Object)(object)NetworkSingleton.Instance != (Object)null) { RestoreDuration(NetworkSingleton.Instance); RestoreNativeTarget(NetworkSingleton.Instance); } if (uiWasOpen && (int)InputEvents.ActiveLayer == 3) { InputEvents.ActiveLayer = previousLayer; } if (roundTravel != null) { roundTravel.Dispose(); } if (npcCombat != null) { npcCombat.Dispose(); } if (gunShop != null) { gunShop.Dispose(); } if (gunReload != null) { gunReload.Dispose(); } if (bodyRepair != null) { bodyRepair.Dispose(); } if (bedWake != null) { bedWake.Dispose(); } if (wardrobe != null) { wardrobe.Dispose(); } if (neckwear != null) { neckwear.Dispose(); } if (dayEndOverlay != null) { dayEndOverlay.Dispose(); } if (betDisplay != null) { betDisplay.Dispose(); } if (allIn != null) { allIn.Dispose(); } if (network != null) { network.Dispose(); } if (ui != null) { ui.Dispose(); } if (harmony != null) { harmony.UnpatchSelf(); } Instance = null; } internal void NativeResetLog(string message) { ((BaseUnityPlugin)this).Logger.LogInfo((object)message); } private static ulong LocalPlayerId() { if ((Object)(object)NetworkClient.localPlayer == (Object)null) { return 0uL; } PlayerProfile component = ((Component)NetworkClient.localPlayer).GetComponent(); if (!((Object)(object)component == (Object)null)) { return component.steamId; } return 0uL; } private ulong[] ConnectedVoters() { if (!TryConnectedVoters(out var players, out var _)) { return null; } return players; } private bool TryConnectedVoters(out ulong[] players, out SessionIdentityStatus status) { players = new ulong[0]; status = SessionIdentityStatus.Ready; List list = new List(); foreach (NetworkConnectionToClient value in NetworkServer.connections.Values) { list.Add(SessionPlayerProof(value)); if (list.Count > 32) { status = SessionIdentityStatus.TooManyPlayers; return false; } } return SessionIdentity.TryRoster(list, 32, out players, out status); } private static SessionPlayerIdentity SessionPlayerProof(NetworkConnectionToClient connection) { //IL_0164: Unknown result type (might be due to invalid IL or missing references) SessionPlayerIdentity sessionPlayerIdentity = new SessionPlayerIdentity(); sessionPlayerIdentity.HasConnection = connection != null; SessionPlayerIdentity sessionPlayerIdentity2 = sessionPlayerIdentity; if (connection == null) { return sessionPlayerIdentity2; } sessionPlayerIdentity2.LiveConnection = NetworkServer.active && NetworkServer.connections.TryGetValue(connection.connectionId, out var value) && object.ReferenceEquals(value, connection); sessionPlayerIdentity2.Authenticated = ((NetworkConnection)connection).isAuthenticated; sessionPlayerIdentity2.Ready = ((NetworkConnection)connection).isReady; NetworkIdentity identity = ((NetworkConnection)connection).identity; sessionPlayerIdentity2.HasIdentity = (Object)(object)identity != (Object)null; if ((Object)(object)identity == (Object)null) { return sessionPlayerIdentity2; } sessionPlayerIdentity2.OwnsIdentity = object.ReferenceEquals(identity.connectionToClient, connection); sessionPlayerIdentity2.SpawnedIdentity = identity.netId != 0 && NetworkServer.spawned.TryGetValue(identity.netId, out var value2) && object.ReferenceEquals(value2, identity); PlayerProfile component = ((Component)identity).GetComponent(); sessionPlayerIdentity2.HasProfile = (Object)(object)component != (Object)null; if ((Object)(object)component == (Object)null) { return sessionPlayerIdentity2; } sessionPlayerIdentity2.ProfileOwnsIdentity = ((NetworkBehaviour)component).isServer && object.ReferenceEquals(((NetworkBehaviour)component).netIdentity, identity); sessionPlayerIdentity2.ProfileSteamId = component.steamId; sessionPlayerIdentity2.ProfileHasSynced = component.hasSynced; sessionPlayerIdentity2.IsLocal = object.ReferenceEquals(connection, NetworkServer.localConnection); if (sessionPlayerIdentity2.IsLocal) { sessionPlayerIdentity2.LocalPlayerMatches = NetworkClient.isConnected && NetworkClient.ready && ((NetworkBehaviour)component).isLocalPlayer && object.ReferenceEquals(NetworkClient.localPlayer, identity); if (SteamManager.Initialized) { sessionPlayerIdentity2.LocalSteamId = SteamUser.GetSteamID().m_SteamID; } } else { Transport active = Transport.active; sessionPlayerIdentity2.SteamTransport = (Object)(object)active != (Object)null && ((object)active).GetType().FullName == "Mirror.FizzySteam.FizzySteamworks" && ((object)active).GetType().Assembly.GetName().Name == "FizzySteamworks"; sessionPlayerIdentity2.RemoteSteamAddress = connection.address; } return sessionPlayerIdentity2; } private string SessionControlHold(bool restart) { //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Invalid comparison between Unknown and I4 //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Invalid comparison between Unknown and I4 //IL_009c: Unknown result type (might be due to invalid IL or missing references) if (!NetworkServer.active) { return "Only the connected host can use this."; } if (!compatible || network == null || !network.ReadyForChaos) { return "Everyone must be connected with the same Chaos Night version."; } if (sessionResetActive || NativeTransitioning) { return "Wait for the current scene change to finish."; } if (PoliceBailBlocking) { return "Finish the shared bail restart first."; } GameManager instance = NetworkSingleton.Instance; if ((Object)(object)instance == (Object)null || (Object)(object)NetworkSingleton.Instance == (Object)null || GetSaveData() == null) { return "Load a saved run first."; } if (restart && (int)instance.state != 1) { return "Start a casino day first."; } if (!restart && (int)instance.state != 1 && (int)instance.state != 0) { return "Available in the house or during a casino day."; } if (!TryConnectedVoters(out var _, out var status)) { return SessionIdentity.HoldMessage(status); } if (restart && (dayStartNativeDay != instance.daysPassed + 1 || string.IsNullOrEmpty(dayStartNativeJson))) { return "The restart snapshot is available from the next casino day. Older mid-day saves have no full snapshot."; } if (HasPendingWagers) { return "Finish or cancel all current casino games first."; } return null; } internal void CaptureRestartBaseline(GameManager manager) { if (!NetworkServer.active || !compatible || sessionResetActive) { return; } try { SaveData val = NativeSessionReset.CaptureLive(NetworkSingleton.Instance, manager); string s = JsonUtility.ToJson((object)val, true); if (Encoding.UTF8.GetByteCount(s) > 65536) { throw new InvalidDataException("Start-of-day snapshot exceeds 64 KiB."); } dayStartNativeJson = s; dayStartNativeDay = manager.daysPassed + 1; RememberPoliceBailBasis(val, manager); } catch (Exception ex) { dayStartNativeJson = null; dayStartNativeDay = 0; ((BaseUnityPlugin)this).Logger.LogWarning((object)("Restart snapshot unavailable: " + ex.GetType().Name + ": " + ex.Message)); } } private void RequestRestartVote() { string text = SessionControlHold(restart: true); if (text != null) { sessionControlNote = text; Present(); return; } ulong num = LocalPlayerId(); if (num != 0) { if (restartVote == null || restartVote.State.Epoch != session) { restartVote = new VoteCoordinator(session, num); } if (restartVote.Start(num, ConnectedVoters(), "Restart day " + dayStartNativeDay + "?")) { resetWaitGames = null; restartCompleted = false; sessionControlNote = null; Changed(); network.Broadcast(); Present(); } } } private void CancelRestartVote() { if (NetworkServer.active && restartVote != null && restartVote.Cancel(LocalPlayerId())) { sessionControlNote = "Restart vote cancelled. The day continues."; Changed(); network.Broadcast(); Present(); } } private void CastLocalVote(bool yes) { if (ui == null || ui.CurrentVoteId <= 0) { return; } if (!NetworkServer.active) { if (guestView != null && CanPlayerVote(guestView, LocalPlayerId()) && network != null) { network.GuestSendVote(ui.CurrentVoteId, guestSession, yes); } } else { CastVote(ui.CurrentVoteId, session, LocalPlayerId(), yes); } } private void ReceiveVote(int id, string epoch, bool yes, NetworkConnectionToClient connection) { if (SessionIdentity.TryResolve(SessionPlayerProof(connection), out var steamId)) { CastVote(id, epoch, steamId, yes); } } private void CastVote(int id, string epoch, ulong player, bool yes) { if (NetworkServer.active && !sessionResetActive && restartVote != null && network != null && network.ReadyForChaos && ReconcileVoteRoster() && restartVote.Cast(epoch, id, player, yes)) { Changed(); network.Broadcast(); Present(); TryFinishRestartVote(); } } private void TryFinishRestartVote() { if (restartVote == null || restartVote.State.Status != VoteStatus.Passed || restartVote.State.Consumed || !ReconcileVoteRoster() || restartVote.State.Status != VoteStatus.Passed) { return; } string text = SessionControlHold(restart: true); if (text != null) { sessionControlNote = "Vote passed. " + text; Changed(); return; } if (resetWaitGames == null) { resetWaitGames = Object.FindObjectsByType((FindObjectsSortMode)0); } GameBase[] array = resetWaitGames; foreach (GameBase val in array) { if ((Object)(object)val != (Object)null && val.isPlaying) { sessionControlNote = "Vote passed. Finish or cancel current casino games to restart."; Changed(); return; } } try { SaveData val2 = JsonUtility.FromJson(dayStartNativeJson); SaveData saveData = GetSaveData(); if (val2 == null || saveData == null || val2.saveName != saveData.saveName || val2.daysPassed + 1 != dayStartNativeDay) { restartVote.Cancel(LocalPlayerId()); sessionControlNote = "Restart cancelled: the saved starting progress does not match this run."; Changed(); } else if (restartVote.TryConsumePassed(session, restartVote.State.Id)) { StartSessionReload(val2, fresh: false); } } catch (Exception ex) { sessionControlNote = "Restart could not finish: " + ex.Message; ((BaseUnityPlugin)this).Logger.LogError((object)ex); Changed(); Present(); } } private bool AnyCasinoGameRunning() { GameBase[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (GameBase val in array) { if ((Object)(object)val != (Object)null && val.isPlaying) { return true; } } return false; } private void OnFreshRun(string dayText, string moneyText) { //IL_0121: Unknown result type (might be due to invalid IL or missing references) lastRecoveryPath = null; string text = SessionControlHold(restart: false); if (text != null) { sessionControlNote = text; Present(); return; } if (restartVote != null && (restartVote.State.Status == VoteStatus.Pending || (restartVote.State.Status == VoteStatus.Passed && !restartVote.State.Consumed))) { sessionControlNote = "Cancel the restart vote before replacing the run."; Present(); return; } if (!SessionInput.TryDay(dayText, out var day)) { sessionControlNote = "Enter a whole day from 1 to 9999."; Present(); return; } if (!SessionInput.TryCash(moneyText, out var mantissa, out var exponent)) { sessionControlNote = "Enter cash such as 1000, 1.5K or 2M. Use a decimal point; zero is allowed."; Present(); return; } BigNumber cash = default(BigNumber); ((BigNumber)(ref cash))..ctor(mantissa, exponent); double num = ((BigNumber)(ref cash)).ToDouble(); if (double.IsInfinity(num) || double.IsNaN(num) || (mantissa > 0.0 && num <= 0.0)) { sessionControlNote = "That cash amount cannot be displayed safely by the game."; Present(); return; } if (AnyCasinoGameRunning()) { sessionControlNote = "Finish or cancel all current casino games first."; Present(); return; } try { StartSessionReload(NativeSessionReset.PrepareFresh(NetworkSingleton.Instance, NetworkSingleton.Instance, day, cash), fresh: true); } catch (Exception ex) { sessionControlNote = ((lastRecoveryPath == null) ? ("The run was not changed: " + ex.Message) : "The reset could not finish. A recovery backup was retained."); ((BaseUnityPlugin)this).Logger.LogWarning((object)ex); Present(); } } private void StartSessionReload(SaveData snapshot, bool fresh) { //IL_0193: Unknown result type (might be due to invalid IL or missing references) //IL_0198: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_0260: Unknown result type (might be due to invalid IL or missing references) //IL_0265: Unknown result type (might be due to invalid IL or missing references) if (sessionResetActive) { return; } SaveManager instance = NetworkSingleton.Instance; GameManager instance2 = NetworkSingleton.Instance; Action action = CaptureDirectorRecovery(); Action action2 = ((fresh && gunReload != null) ? gunReload.CaptureResetRecovery() : ((Action)delegate { })); SaveData saveData = GetSaveData(); Action action3 = ((fresh && policeBailJournal != null && saveData != null) ? policeBailJournal.CaptureRecovery(saveData.saveName, saveData.seed) : ((Action)delegate { })); Engine engine = null; if (!fresh && HasMatchingRetrySnapshot(this.engine.State.Day, instance2.daysPassed, preDayEngineJson)) { int num = ((this.engine.State.BetItAllConsumedDay == this.engine.State.Day) ? this.engine.State.Day : (-1)); engine = new Engine(ReadRules()); engine.Restore(ChaosJson.Read(preDayEngineJson)); if (num >= 0) { engine.PreserveBetItAllUseForDay(num); } } SaveData val = NativeSessionReset.CaptureLive(instance, instance2); string text = (lastRecoveryPath = saves.CreateRecoveryBackup(val.saveName, NativePath(val.saveName), JsonUtility.ToJson((object)val, true), Path.Combine(Paths.ConfigPath, "ChaosNight", "recovery"))); sessionResetActive = true; resetHostConnection = (NetworkConnectionToClient)(object)NetworkServer.localConnection; Scene activeScene = SceneManager.GetActiveScene(); previousHomeSceneHandle = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle); resetStartedAt = Time.unscaledTime; nextResetDiagnostic = resetStartedAt + 3f; pendingHomeRestore = snapshot; sessionControlNote = (fresh ? ("Starting a fresh day " + (snapshot.daysPassed + 1) + " with your chosen shared cash.") : ("Vote passed. Restoring day " + dayStartNativeDay + " to its starting progress.")); ui.CloseForSessionReload(); RefreshOverlayInput(); try { if (!fresh) { RestoreNativeTarget(instance2); RestoreDuration(instance2); if (engine != null) { this.engine = engine; stagedShopSpend = preDayShopSpend; } skippedNativeDay = -1; inDay = false; daySuspended = false; nativeChaosCashDay = BigNumber.Zero; wagers.Clear(); } NativeSessionReset.ApplyData(instance, snapshot, fresh); if (fresh && gunReload != null) { gunReload.StartFreshRun(); } if (fresh) { BeginPoliceBailFresh(snapshot); } checkpointLoaded = true; lastSave = snapshot.saveName; holdReason = null; SaveCheckpoint(GetSaveData()); EnsurePoliceBailCheckpoint(snapshot); Changed(); network.Broadcast(); Present(); ((BaseUnityPlugin)this).Logger.LogInfo((object)("Session reset recovery backup: " + text)); NativeSessionReset.ReloadHome(instance2); } catch { pendingHomeRestore = null; try { NativeSessionReset.ApplyData(instance, val, resetDefaults: false); } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)("Session reset rollback failed; recovery backup retained: " + ex)); } finally { try { action(); SaveCheckpoint(GetSaveData()); WritePoliceBailJournal(); } catch (Exception ex2) { ((BaseUnityPlugin)this).Logger.LogError((object)("Director recovery failed; its backup was retained: " + ex2)); } try { action3(); } catch (Exception ex3) { ((BaseUnityPlugin)this).Logger.LogError((object)("Bail journal recovery failed; its backup was retained: " + ex3)); } try { action2(); } catch (Exception ex4) { ((BaseUnityPlugin)this).Logger.LogError((object)("Magazine recovery failed; its backup was retained: " + ex4)); } sessionResetActive = false; } throw; } } private void TickSessionControls() { //IL_04bc: Unknown result type (might be due to invalid IL or missing references) //IL_04c2: Invalid comparison between Unknown and I4 //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0243: Unknown result type (might be due to invalid IL or missing references) //IL_0248: Unknown result type (might be due to invalid IL or missing references) //IL_024c: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0273: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) //IL_011b: Unknown result type (might be due to invalid IL or missing references) //IL_0131: Unknown result type (might be due to invalid IL or missing references) //IL_0136: 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_0316: Unknown result type (might be due to invalid IL or missing references) //IL_031b: Unknown result type (might be due to invalid IL or missing references) //IL_037e: Unknown result type (might be due to invalid IL or missing references) //IL_0383: Unknown result type (might be due to invalid IL or missing references) //IL_0387: Unknown result type (might be due to invalid IL or missing references) if (sessionResetActive) { if (!NetworkServer.active || !object.ReferenceEquals(resetHostConnection, NetworkServer.localConnection)) { sessionResetActive = false; pendingHomeRestore = null; return; } if (Time.unscaledTime - resetStartedAt > 240f) { sessionResetActive = false; pendingHomeRestore = null; sessionControlNote = "The scene reload did not finish. A recovery backup was retained."; if (PoliceBailBlocking) { HoldPoliceBail(sessionControlNote); } Changed(); return; } SaveManager instance = NetworkSingleton.Instance; GameManager instance2 = NetworkSingleton.Instance; if (Time.unscaledTime >= nextResetDiagnostic) { nextResetDiagnostic = Time.unscaledTime + 5f; HashSet hashSet = (((Object)(object)instance2 == (Object)null || SceneReadyField == null) ? null : (SceneReadyField.GetValue(instance2) as HashSet)); int num = ((!((Object)(object)instance2 == (Object)null) && !(SceneExpectedField == null)) ? ((int)SceneExpectedField.GetValue(instance2)) : 0); ManualLogSource logger = ((BaseUnityPlugin)this).Logger; object[] array = new object[17] { "Session reset waiting: scene=", null, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null }; Scene activeScene = SceneManager.GetActiveScene(); array[1] = ((Scene)(ref activeScene)).name; array[2] = ", handle="; Scene activeScene2 = SceneManager.GetActiveScene(); array[3] = ((Scene)(ref activeScene2)).handle; array[4] = ", nativeTransition="; array[5] = NativeTransitioning; array[6] = ", mirrorLoading="; array[7] = NetworkServer.isLoadingScene; array[8] = ", nativeLoaded="; array[9] = (Object)(object)instance != (Object)null && NativeLoadedField != null && (bool)NativeLoadedField.GetValue(instance); array[10] = ", sceneReady="; array[11] = hashSet?.Count ?? 0; array[12] = "/"; array[13] = num; array[14] = ", partySynced="; array[15] = ConnectedVoters() != null; array[16] = "."; logger.LogInfo((object)string.Concat(array)); } if (pendingHomeRestore == null) { return; } Scene activeScene3 = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene3)).name == "HomeScene")) { return; } Scene activeScene4 = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene4)).handle != SceneHandle.op_Implicit(previousHomeSceneHandle)) || !((Object)(object)instance2 != (Object)null) || (int)instance2.state != 0 || NativeTransitioning || !((Object)(object)instance != (Object)null) || !(NativeLoadedField != null) || !(bool)NativeLoadedField.GetValue(instance) || ConnectedVoters() == null || !NativeHomeInitialized(instance2)) { return; } try { SaveData saveData = GetSaveData(); if (!ChaosResetTicketBaseline.MatchesPendingRestore(saveData, pendingHomeRestore)) { throw new InvalidOperationException("The selected run changed while the reset was loading."); } NativeSessionReset.ApplyData(instance, pendingHomeRestore, resetDefaults: false); checkpointLoaded = true; lastSave = pendingHomeRestore.saveName; nativeChaosCashDay = BigNumber.Zero; inDay = false; daySuspended = false; SaveCheckpoint(GetSaveData()); EnsurePoliceBailCheckpoint(pendingHomeRestore); ManualLogSource logger2 = ((BaseUnityPlugin)this).Logger; object[] array2 = new object[5] { "Session reset completed after native Home readiness: day=", pendingHomeRestore.daysPassed + 1, ", handle=", null, null }; Scene activeScene5 = SceneManager.GetActiveScene(); array2[3] = ((Scene)(ref activeScene5)).handle; array2[4] = "."; logger2.LogInfo((object)string.Concat(array2)); sessionControlNote = "Ready in bed on day " + (pendingHomeRestore.daysPassed + 1) + "."; if (restartVote != null && restartVote.State.Consumed) { restartCompleted = true; } pendingHomeRestore = null; sessionResetActive = false; PoliceBailHomeReady(); Changed(); network.Broadcast(); Present(); return; } catch (Exception ex) { ((BaseUnityPlugin)this).Logger.LogError((object)ex); sessionResetActive = false; pendingHomeRestore = null; sessionControlNote = "The final restore failed. A recovery backup was retained."; if (PoliceBailBlocking) { HoldPoliceBail(sessionControlNote); } Changed(); return; } } if (!NetworkServer.active || restartVote == null || restartVote.State.Consumed || (restartVote.State.Status != VoteStatus.Pending && restartVote.State.Status != VoteStatus.Passed)) { return; } if ((Object)(object)NetworkSingleton.Instance == (Object)null || (int)NetworkSingleton.Instance.state != 1 || NativeTransitioning) { if (restartVote.Cancel(LocalPlayerId())) { sessionControlNote = "Restart vote cancelled because the day changed."; Changed(); } } else if (network != null && network.ReadyForChaos && ReconcileVoteRoster()) { TryFinishRestartVote(); } } private static bool CanPlayerVote(UiState view, ulong player) { if (view != null && view.VoteActive && player != 0 && view.VoteMembers != null && view.VoteVoters != null && Array.IndexOf(view.VoteMembers, player) >= 0) { return Array.IndexOf(view.VoteVoters, player) < 0; } return false; } private void AddSessionControls(UiState view) { string text = SessionControlHold(restart: true); string text2 = SessionControlHold(restart: false); bool flag = restartVote != null && (restartVote.State.Status == VoteStatus.Pending || (restartVote.State.Status == VoteStatus.Passed && !restartVote.State.Consumed)); view.CanRequestRestartVote = text == null && !flag; view.CanCancelVote = flag && !sessionResetActive; view.CanStartFreshRun = text2 == null && !flag; view.RestartVoteHint = (flag ? "A vote is open. Every eligible player chooses once; the host may cancel." : (text ?? "Ask everyone to restart today. More than half of the party must vote Yes.")); view.FreshRunHint = text2 ?? sessionControlNote ?? "Replaces the current run. A recovery backup is saved before anything changes."; view.FreshRunMinDay = 1; view.FreshRunMaxDay = 9999; view.FreshRunDefaultDay = "1"; view.FreshRunDefaultMoney = ((view.IsHost && NativeSessionReset.TryRecommendedFreshCash(NetworkSingleton.Instance, 1, out var text3)) ? text3 : ""); view.VoteMembers = ((restartVote == null) ? new ulong[0] : ((ulong[])restartVote.State.Members.Clone())); List list = new List(); if (restartVote != null) { foreach (Ballot ballot in restartVote.State.Ballots) { list.Add(ballot.Player); } } view.VoteVoters = list.ToArray(); view.VotePlayers = view.VoteMembers.Length; view.VoteId = ((restartVote != null) ? restartVote.State.Id : 0); view.VoteEpoch = ((restartVote == null) ? "" : restartVote.State.Epoch); view.VoteRequired = ((view.VotePlayers != 0) ? (view.VotePlayers / 2 + 1) : 0); view.VoteYes = ((restartVote != null) ? restartVote.YesCount : 0); view.VoteNo = ((restartVote != null) ? restartVote.NoCount : 0); view.VoteActive = restartVote != null && restartVote.State.Status == VoteStatus.Pending; view.SessionReloading = sessionResetActive; view.VoteClosed = restartVote != null && (restartVote.State.Status == VoteStatus.Failed || restartVote.State.Status == VoteStatus.Cancelled || (restartVote.State.Status == VoteStatus.Passed && restartVote.State.Consumed)); view.VoteVisible = restartVote != null && restartVote.State.Id > 0 && !sessionResetActive; view.VoteStatus = ((restartVote == null) ? "" : ((restartVote.State.Status == VoteStatus.Pending) ? (restartVote.State.Prompt + "\nReturn to bed with the day's starting progress.") : ((restartVote.State.Status != VoteStatus.Passed) ? ((restartVote.State.Status == VoteStatus.Failed) ? "Restart vote failed. The day continues." : "Restart vote cancelled.") : ((!restartVote.State.Consumed) ? "Vote passed. Waiting for casino games to finish." : (restartCompleted ? "Vote passed. The day restarted." : "Vote passed, but restart did not finish. The host can try a new vote."))))); view.CanVote = CanPlayerVote(view, LocalPlayerId()) && network != null && network.ReadyForChaos; view.VoteHint = "Votes are final. More than half must choose Yes."; } private static void ValidateVoteView(UiState view) { if (view.VoteMembers == null || view.VoteVoters == null || view.VoteMembers.Length > 32 || view.VoteVoters.Length > view.VoteMembers.Length || view.VoteId < 0 || view.VoteYes < 0 || view.VoteNo < 0 || view.VotePlayers != view.VoteMembers.Length || view.VoteYes + view.VoteNo != view.VoteVoters.Length || view.VoteRequired != ((view.VotePlayers != 0) ? (view.VotePlayers / 2 + 1) : 0) || (view.VoteId > 0 && (string.IsNullOrEmpty(view.VoteEpoch) || view.VoteEpoch.Length != 32)) || (view.VoteActive && (view.VoteId <= 0 || view.VotePlayers == 0))) { throw new InvalidDataException("Invalid restart-vote view."); } HashSet hashSet = new HashSet(); HashSet hashSet2 = new HashSet(); ulong[] voteMembers = view.VoteMembers; foreach (ulong num in voteMembers) { if (num == 0 || !hashSet.Add(num)) { throw new InvalidDataException("Invalid voter roster."); } } ulong[] voteVoters = view.VoteVoters; foreach (ulong item in voteVoters) { if (!hashSet.Contains(item) || !hashSet2.Add(item)) { throw new InvalidDataException("Invalid vote receipt."); } } } private bool ReconcileVoteRoster() { if (restartVote == null) { return false; } ulong[] array = ConnectedVoters(); if (array == null) { return false; } if (restartVote.ReconcileMembers(array)) { Changed(); } return true; } private static bool NativeHomeInitialized(GameManager manager) { if (SceneReadyField == null || SceneExpectedField == null) { return false; } HashSet hashSet = SceneReadyField.GetValue(manager) as HashSet; int num = (int)SceneExpectedField.GetValue(manager); if (num >= 1 && hashSet != null) { return hashSet.Count >= num; } return false; } private string PrepareEarlierDayNativeRestart(Reward reward, BigNumber cash, GameManager manager) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_013a: Unknown result type (might be due to invalid IL or missing references) //IL_013f: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e5: Expected O, but got Unknown if ((Object)(object)manager == (Object)null || (int)manager.state != 1 || reward.Day >= manager.daysPassed + 1 || dayStartNativeDay != manager.daysPassed + 1 || string.IsNullOrEmpty(dayStartNativeJson)) { return null; } SaveData val = JsonUtility.FromJson(dayStartNativeJson); if (val == null || val.GetMoney() > BigNumber.MaxValue - cash) { throw new InvalidDataException("Earlier reward cannot fit the restart balance."); } val.SetMoney(val.GetMoney() + cash); if (val.profitHistory == null) { val.profitHistory = new List(); } string id = reward.Player.ToString(CultureInfo.InvariantCulture); ProfitHistorySaveData val2 = val.profitHistory.Find((ProfitHistorySaveData item) => item.steamId == id); if (val2 == null) { PlayerProfile val3 = ResolveRewardPlayer(reward.Player); ProfitHistorySaveData val4 = new ProfitHistorySaveData(); val4.steamId = id; val4.playerName = (((Object)(object)val3 == (Object)null) ? ("Player " + id) : val3.playerName); val2 = val4; val.profitHistory.Add(val2); } BigNumber val5 = new BigNumber(val2.profitAmountM, val2.profitAmountE) + cash; ((BigNumber)(ref val5)).WriteSave(ref val2.profitAmount, ref val2.profitAmountM, ref val2.profitAmountE); string text = JsonUtility.ToJson((object)val, true); if (Encoding.UTF8.GetByteCount(text) > 65536) { throw new InvalidDataException("Restart receipt exceeds its save limit."); } return text; } private Action CaptureDirectorRecovery() { //IL_00ac: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c4: Unknown result type (might be due to invalid IL or missing references) //IL_00c9: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_017b: Unknown result type (might be due to invalid IL or missing references) //IL_0180: Unknown result type (might be due to invalid IL or missing references) Action restoreStats = CaptureStatsRecovery(); Action restorePoliceBail = CapturePoliceBailRecovery(); Engine before = new Engine(ReadRules()); before.Restore(ChaosJson.Read(ChaosJson.Write(engine.State))); bool wasInDay = inDay; bool wasSuspended = daySuspended; bool wasQuota = quotaChanged; bool wasLoaded = checkpointLoaded; bool hadBaseline = nativeBaselineLoaded; int skipped = skippedNativeDay; int baselineDay = dayStartNativeDay; BigNumber quota = originalQuota; BigNumber adjusted = adjustedQuota; BigNumber delivered = nativeChaosCashDay; BigNumber startBalance = nativeStartBalance; float duration = originalDuration; long tickets = dayStartTickets; long spend = stagedShopSpend; long beforeSpend = preDayShopSpend; long startTickets = nativeStartTickets; string beforeJson = preDayEngineJson; string nativeJson = dayStartNativeJson; string saveName = lastSave; string held = holdReason; string epoch = session; VoteCoordinator vote = restartVote; MoneyManager money = NetworkSingleton.Instance; GameManager manager = NetworkSingleton.Instance; BigNumber actualDayCash = money.dayStartBalance; long actualDayTickets = money.dayStartTicketBalance; bool nativeEndless = manager.EndlessMode; Dictionary nativeFlags = new Dictionary(); string[] array = new string[6] { "_timer", "_dayDuration", "_roundTimerEnabled", "_isTimeOver", "_isTransitioning", "k__BackingField" }; foreach (string text in array) { FieldInfo fieldInfo = AccessTools.Field(typeof(GameManager), text); if (fieldInfo == null) { throw new MissingFieldException("Cannot capture the native restart state: " + text); } nativeFlags.Add(fieldInfo, fieldInfo.GetValue(manager)); } return delegate { //IL_0094: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Unknown result type (might be due to invalid IL or missing references) //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_00b6: 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_00c7: 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) restoreStats(); restorePoliceBail(); engine = before; inDay = wasInDay; daySuspended = wasSuspended; quotaChanged = wasQuota; checkpointLoaded = wasLoaded; nativeBaselineLoaded = hadBaseline; skippedNativeDay = skipped; originalQuota = quota; adjustedQuota = adjusted; nativeChaosCashDay = delivered; nativeStartBalance = startBalance; originalDuration = duration; dayStartTickets = tickets; stagedShopSpend = spend; preDayShopSpend = beforeSpend; nativeStartTickets = startTickets; preDayEngineJson = beforeJson; dayStartNativeJson = nativeJson; dayStartNativeDay = baselineDay; lastSave = saveName; holdReason = held; session = epoch; restartVote = vote; rewardPlayers.Clear(); Changed(); money.Network_dayStartBalanceM = actualDayCash.Mantissa; money.Network_dayStartBalanceE = actualDayCash.Exponent; money.NetworkdayStartTicketBalance = actualDayTickets; foreach (KeyValuePair item in nativeFlags) { item.Key.SetValue(manager, item.Value); } manager.Network_timer = (float)nativeFlags[AccessTools.Field(typeof(GameManager), "_timer")]; manager.Network_dayDuration = (float)nativeFlags[AccessTools.Field(typeof(GameManager), "_dayDuration")]; manager.Network_roundTimerEnabled = (bool)nativeFlags[AccessTools.Field(typeof(GameManager), "_roundTimerEnabled")]; manager.Network_endlessMode = nativeEndless; }; } } [HarmonyPatch(typeof(ChallengeManager), "UpdateActiveChallengesUI")] internal static class ChaosNativeChallengeRefreshPatch { private static void Postfix(ChallengeManager __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.BindNativeChallenge(__instance); } } } [HarmonyPatch(typeof(ChallengeManager), "OnStartClient")] internal static class ChaosNativeChallengeStartPatch { private static void Postfix(ChallengeManager __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.BindNativeChallenge(__instance); } } } [HarmonyPatch(typeof(UpgradeUI), "UpdateUpgradeUI")] internal static class ChaosNativeUpgradeRefreshPatch { private static void Postfix(UpgradeUI __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.BindNativeUpgradePresentation(__instance); } } } [HarmonyPatch(typeof(UpgradeUI), "ClearUpgradeUI")] internal static class ChaosNativeUpgradeClearPatch { private static void Postfix(UpgradeUI __instance) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.BindNativeUpgradePresentation(__instance); } } } public struct NeckPoint { public double X; public double Y; public double Z; public NeckPoint(double x, double y, double z) { X = x; Y = y; Z = z; } } public sealed class NeckwearEnvelope { private struct Edge { public NeckPoint A; public NeckPoint B; } public const int HeightBands = 64; public const int DirectionBands = 128; public const int MaximumVertices = 32768; public const double Clearance = 0.025; private readonly List[] slices = new List[64]; private readonly double minY; private readonly double maxY; private readonly double step; public NeckwearEnvelope(NeckPoint[] clothing, int[] triangles) { if (clothing == null || triangles == null || clothing.Length < 3 || clothing.Length > 32768 || triangles.Length % 3 != 0 || triangles.Length > 196608) { throw new ArgumentException("Unsupported clothing geometry."); } minY = double.PositiveInfinity; maxY = double.NegativeInfinity; for (int i = 0; i < clothing.Length; i++) { NeckPoint p = clothing[i]; if (!Finite(p)) { throw new ArgumentException("Invalid clothing vertex."); } minY = Math.Min(minY, p.Y); maxY = Math.Max(maxY, p.Y); } step = (maxY - minY) / 64.0; if (step <= 1E-08) { throw new ArgumentException("Clothing has no height."); } foreach (int num in triangles) { if (num < 0 || num >= clothing.Length) { throw new ArgumentException("Invalid clothing triangle."); } } for (int k = 0; k < 64; k++) { slices[k] = new List(); } for (int l = 0; l < triangles.Length; l += 3) { NeckPoint item = clothing[triangles[l]]; NeckPoint item2 = clothing[triangles[l + 1]]; NeckPoint item3 = clothing[triangles[l + 2]]; int num2 = Band(Math.Min(item.Y, Math.Min(item2.Y, item3.Y))); int num3 = Band(Math.Max(item.Y, Math.Max(item2.Y, item3.Y))); for (int m = num2; m <= num3; m++) { List polygon = new List(3) { item, item2, item3 }; polygon = Clip(polygon, minY + (double)m * step - 1E-08, above: true); polygon = Clip(polygon, minY + (double)(m + 1) * step + 1E-08, above: false); for (int n = 0; n < polygon.Count; n++) { slices[m].Add(new Edge { A = polygon[n], B = polygon[(n + 1) % polygon.Count] }); } } } } private static List Clip(List polygon, double height, bool above) { List list = new List(6); if (polygon.Count == 0) { return list; } NeckPoint neckPoint = polygon[polygon.Count - 1]; bool flag = (above ? (neckPoint.Y >= height) : (neckPoint.Y <= height)); foreach (NeckPoint item in polygon) { bool flag2 = (above ? (item.Y >= height) : (item.Y <= height)); if (flag2 != flag) { double num = (height - neckPoint.Y) / (item.Y - neckPoint.Y); list.Add(new NeckPoint(neckPoint.X + (item.X - neckPoint.X) * num, height, neckPoint.Z + (item.Z - neckPoint.Z) * num)); } if (flag2) { list.Add(item); } neckPoint = item; flag = flag2; } return list; } private int Band(double y) { return Math.Max(0, Math.Min(63, (int)Math.Floor((y - minY) / step))); } private static double Cross(double ax, double az, double bx, double bz) { return ax * bz - az * bx; } public double Radius(double x, double y, double z) { double num = Math.Sqrt(x * x + z * z); if (num < 1E-08 || y < minY - 1E-08 || y > maxY + 1E-08) { return 0.0; } x /= num; z /= num; double num2 = 0.0; foreach (Edge item in slices[Band(y)]) { double bx = item.B.X - item.A.X; double bz = item.B.Z - item.A.Z; double num3 = Cross(x, z, bx, bz); if (Math.Abs(num3) < 1E-10) { if (Math.Abs(Cross(item.A.X, item.A.Z, x, z)) < 1E-08) { num2 = Math.Max(num2, Math.Max(item.A.X * x + item.A.Z * z, item.B.X * x + item.B.Z * z)); } continue; } double num4 = Cross(item.A.X, item.A.Z, bx, bz) / num3; double num5 = Cross(item.A.X, item.A.Z, x, z) / num3; if (num4 >= 0.0 && num5 >= -1E-08 && num5 <= 1.00000001) { num2 = Math.Max(num2, num4); } } return num2; } public NeckPoint[] Fit(NeckPoint[] neckwear) { if (neckwear == null || neckwear.Length > 32768) { throw new ArgumentException("Unsupported neckwear geometry."); } double[] array = new double[128]; for (int i = 0; i < neckwear.Length; i++) { NeckPoint p = neckwear[i]; if (!Finite(p)) { throw new ArgumentException("Invalid neckwear vertex."); } double num = Math.Sqrt(p.X * p.X + p.Z * p.Z); if (!(num < 1E-08)) { double num2 = Radius(p.X, p.Y, p.Z); if (num2 > 0.0) { array[Direction(p)] = Math.Max(array[Direction(p)], Math.Max(0.0, num2 + 0.025 - num)); } } } NeckPoint[] array2 = new NeckPoint[neckwear.Length]; for (int j = 0; j < neckwear.Length; j++) { NeckPoint neckPoint = neckwear[j]; double num3 = Math.Sqrt(neckPoint.X * neckPoint.X + neckPoint.Z * neckPoint.Z); if (num3 < 1E-08) { array2[j] = neckPoint; continue; } int num4 = Direction(neckPoint); double num5 = Math.Max(array[num4], Math.Max(array[(num4 + 128 - 1) % 128], array[(num4 + 1) % 128])); double num6 = (num3 + num5) / num3; ref NeckPoint reference = ref array2[j]; reference = new NeckPoint(neckPoint.X * num6, neckPoint.Y, neckPoint.Z * num6); } return array2; } private static int Direction(NeckPoint p) { return Math.Min(127, (int)((Math.Atan2(p.Z, p.X) + Math.PI) / (Math.PI * 2.0) * 128.0)); } private static bool Finite(NeckPoint p) { if (!double.IsNaN(p.X) && !double.IsNaN(p.Y) && !double.IsNaN(p.Z) && !double.IsInfinity(p.X) && !double.IsInfinity(p.Y)) { return !double.IsInfinity(p.Z); } return false; } } public sealed class ChaosNeckwear : IDisposable { private sealed class Wear { internal MeshFilter Neck; internal MeshFilter Clothing; internal MeshFilter Shadow; internal Mesh Source; internal Mesh Fitted; internal Mesh Garment; internal Mesh ShadowSource; internal Matrix4x4 Relative; internal bool Prepared; internal MeshFilter[] Parts; internal Mesh[] PartMeshes; internal Matrix4x4[] PartRelative; } [HarmonyPatch(typeof(PlayerCustomization), "ApplyCosmetic")] private static class Apply { [HarmonyPriority(800)] private static void Prefix(PlayerCustomization __instance) { if (Active != null) { Active.Before(__instance); } } [HarmonyAfter(new string[] { "co.oasistechnologies.gwyf.itsthepolice" })] [HarmonyPriority(0)] private static void Postfix(PlayerCustomization __instance) { if (Active != null) { Active.Refresh(__instance); } } } [HarmonyPatch(typeof(PlayerCustomization), "UserCode_RpcClearCategory__CosmeticType")] private static class ClearCategory { [HarmonyPriority(0)] private static void Postfix(PlayerCustomization __instance) { if (Active != null) { Active.Refresh(__instance); } } } [HarmonyPatch(typeof(PlayerCustomization), "ClearAllCosmetics")] private static class ClearAll { [HarmonyPriority(0)] private static void Postfix(PlayerCustomization __instance) { if (Active != null) { Active.Refresh(__instance); } } } internal static ChaosNeckwear Active; private static readonly FieldInfo Neck = AccessTools.Field(typeof(PlayerCustomization), "neckwear"); private static readonly FieldInfo Clothing = AccessTools.Field(typeof(PlayerCustomization), "clothing"); private static readonly FieldInfo Shadows = AccessTools.Field(typeof(PlayerCustomization), "shadowOnlyClones"); private readonly Func allowed; private readonly Action log; private readonly Dictionary wears = new Dictionary(); private readonly List stale = new List(); private float nextTick; private bool disposed; private bool reported; public ChaosNeckwear(Func canUse, Action logger) { if (canUse == null || Neck == null || Clothing == null || Shadows == null) { throw new InvalidOperationException("Native neckwear attachments are unavailable."); } if (Active != null) { throw new InvalidOperationException("Neckwear presentation already initialized."); } allowed = canUse; log = logger; Active = this; } internal void Before(PlayerCustomization player) { if ((Object)(object)player != (Object)null && wears.TryGetValue(player, out var value)) { Restore(value); } } internal void Refresh(PlayerCustomization player) { //IL_00eb: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_01b9: Unknown result type (might be due to invalid IL or missing references) //IL_01ba: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Unknown result type (might be due to invalid IL or missing references) //IL_0266: Unknown result type (might be due to invalid IL or missing references) //IL_026b: Unknown result type (might be due to invalid IL or missing references) //IL_0270: Unknown result type (might be due to invalid IL or missing references) //IL_028a: Unknown result type (might be due to invalid IL or missing references) //IL_028c: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Unknown result type (might be due to invalid IL or missing references) //IL_016b: Unknown result type (might be due to invalid IL or missing references) //IL_02de: Unknown result type (might be due to invalid IL or missing references) //IL_02e3: Unknown result type (might be due to invalid IL or missing references) //IL_02e7: Unknown result type (might be due to invalid IL or missing references) //IL_02e9: Unknown result type (might be due to invalid IL or missing references) //IL_02ee: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_0452: Unknown result type (might be due to invalid IL or missing references) //IL_0457: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0478: Unknown result type (might be due to invalid IL or missing references) //IL_047a: Unknown result type (might be due to invalid IL or missing references) if (disposed || (Object)(object)player == (Object)null || !NetworkClient.active || !allowed()) { return; } try { if (!wears.TryGetValue(player, out var value)) { if (wears.Count >= 24) { return; } value = new Wear(); wears.Add(player, value); } object? value2 = Neck.GetValue(player); MeshFilter val = (MeshFilter)((value2 is MeshFilter) ? value2 : null); object? value3 = Clothing.GetValue(player); MeshFilter val2 = (MeshFilter)((value3 is MeshFilter) ? value3 : null); if ((Object)(object)val == (Object)null || (Object)(object)val2 == (Object)null || (Object)(object)val.sharedMesh == (Object)null || (Object)(object)val2.sharedMesh == (Object)null) { Restore(value); value.Neck = val; value.Clothing = val2; } else { if (!((Component)val).gameObject.activeInHierarchy || !((Component)val2).gameObject.activeInHierarchy) { return; } Matrix4x4 val3 = ((Component)val).transform.worldToLocalMatrix * ((Component)val2).transform.localToWorldMatrix; MeshFilter[] array = VisibleClothing(val2); if ((Object)(object)value.Neck == (Object)(object)val && (Object)(object)value.Clothing == (Object)(object)val2 && value.Prepared && (Object)(object)val.sharedMesh == (Object)(object)(((Object)(object)value.Fitted != (Object)null) ? value.Fitted : value.Source) && (Object)(object)val2.sharedMesh == (Object)(object)value.Garment && Same(val3, value.Relative) && SameParts(value, array, ((Component)val).transform)) { return; } Restore(value); value.Neck = val; value.Clothing = val2; value.Source = val.sharedMesh; value.Garment = val2.sharedMesh; value.Relative = val3; value.Prepared = true; value.Parts = array; value.PartMeshes = (Mesh[])(object)new Mesh[array.Length]; value.PartRelative = (Matrix4x4[])(object)new Matrix4x4[array.Length]; if (!value.Source.isReadable || !value.Garment.isReadable || value.Source.vertexCount > 32768 || value.Garment.vertexCount > 32768) { return; } List list = new List(); List list2 = new List(); for (int i = 0; i < array.Length; i++) { Mesh sharedMesh = array[i].sharedMesh; Matrix4x4 val4 = ((Component)val).transform.worldToLocalMatrix * ((Component)array[i]).transform.localToWorldMatrix; value.PartMeshes[i] = sharedMesh; value.PartRelative[i] = val4; if (sharedMesh.isReadable) { if (list.Count + sharedMesh.vertexCount > 32768) { throw new ArgumentException("Clothing detail vertex limit."); } int count = list.Count; Vector3[] vertices = sharedMesh.vertices; foreach (Vector3 val5 in vertices) { Vector3 val6 = ((Matrix4x4)(ref val4)).MultiplyPoint3x4(val5); list.Add(new NeckPoint(val6.x, val6.y, val6.z)); } int[] triangles = sharedMesh.triangles; if (list2.Count + triangles.Length > 196608) { throw new ArgumentException("Clothing detail triangle limit."); } int[] array2 = triangles; foreach (int num in array2) { list2.Add(count + num); } } } Vector3[] vertices2 = value.Source.vertices; NeckPoint[] array3 = new NeckPoint[vertices2.Length]; for (int l = 0; l < vertices2.Length; l++) { ref NeckPoint reference = ref array3[l]; reference = new NeckPoint(vertices2[l].x, vertices2[l].y, vertices2[l].z); } NeckPoint[] array4 = new NeckwearEnvelope(list.ToArray(), list2.ToArray()).Fit(array3); bool flag = false; Vector3 val7 = default(Vector3); for (int m = 0; m < array4.Length; m++) { ((Vector3)(ref val7))..ctor((float)array4[m].X, (float)array4[m].Y, (float)array4[m].Z); Vector3 val8 = val7 - vertices2[m]; if (((Vector3)(ref val8)).sqrMagnitude > 1E-10f) { flag = true; } vertices2[m] = val7; } if (!flag) { return; } value.Fitted = Object.Instantiate(value.Source); ((Object)value.Fitted).name = "Chaos fitted neckwear"; value.Fitted.vertices = vertices2; value.Fitted.RecalculateBounds(); value.Fitted.RecalculateNormals(); val.sharedMesh = value.Fitted; if (Shadows.GetValue(player) is Dictionary dictionary && dictionary.TryGetValue((CosmeticType)4, out var value4) && (Object)(object)value4 != (Object)null) { value.Shadow = value4.GetComponent(); if ((Object)(object)value.Shadow != (Object)null) { value.ShadowSource = value.Shadow.sharedMesh; value.Shadow.sharedMesh = value.Fitted; } } } } catch (Exception ex) { if (wears.TryGetValue(player, out var value5)) { Restore(value5); } if (!reported) { reported = true; if (log != null) { log("Neckwear fitting kept original presentation: " + ex.GetBaseException().Message); } } } } private static bool Same(Matrix4x4 a, Matrix4x4 b) { for (int i = 0; i < 16; i++) { if (Mathf.Abs(((Matrix4x4)(ref a))[i] - ((Matrix4x4)(ref b))[i]) > 0.0002f) { return false; } } return true; } private static MeshFilter[] VisibleClothing(MeshFilter garment) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0055: Invalid comparison between Unknown and I4 List list = new List(); list.Add(garment); MeshFilter[] componentsInChildren = ((Component)garment).GetComponentsInChildren(false); foreach (MeshFilter val in componentsInChildren) { if ((Object)(object)val == (Object)(object)garment || (Object)(object)val.sharedMesh == (Object)null) { continue; } Renderer component = ((Component)val).GetComponent(); if (!((Object)(object)component == (Object)null) && component.enabled && (int)component.shadowCastingMode != 3) { if (list.Count >= 16) { throw new ArgumentException("Clothing detail part limit."); } list.Add(val); } } return list.ToArray(); } private static bool SameParts(Wear wear, MeshFilter[] parts, Transform neck) { //IL_0045: 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_0057: Unknown result type (might be due to invalid IL or missing references) //IL_0068: Unknown result type (might be due to invalid IL or missing references) if (wear.Parts == null || parts.Length != wear.Parts.Length) { return false; } for (int i = 0; i < parts.Length; i++) { if ((Object)(object)parts[i] != (Object)(object)wear.Parts[i] || (Object)(object)parts[i].sharedMesh != (Object)(object)wear.PartMeshes[i] || !Same(neck.worldToLocalMatrix * ((Component)parts[i]).transform.localToWorldMatrix, wear.PartRelative[i])) { return false; } } return true; } private static void Restore(Wear wear) { if ((Object)(object)wear.Fitted != (Object)null) { if ((Object)(object)wear.Neck != (Object)null && (Object)(object)wear.Neck.sharedMesh == (Object)(object)wear.Fitted) { wear.Neck.sharedMesh = wear.Source; } if ((Object)(object)wear.Shadow != (Object)null && (Object)(object)wear.Shadow.sharedMesh == (Object)(object)wear.Fitted) { wear.Shadow.sharedMesh = wear.ShadowSource; } Object.Destroy((Object)(object)wear.Fitted); } wear.Fitted = null; wear.Shadow = null; wear.ShadowSource = null; wear.Prepared = false; wear.Parts = null; wear.PartMeshes = null; wear.PartRelative = null; } public void Tick() { if (disposed || Time.unscaledTime < nextTick) { return; } nextTick = Time.unscaledTime + 0.5f; stale.Clear(); foreach (KeyValuePair wear in wears) { if ((Object)(object)wear.Key == (Object)null) { stale.Add(wear.Key); } } foreach (PlayerCustomization item in stale) { Restore(wears[item]); wears.Remove(item); } if (!NetworkClient.active || !allowed()) { foreach (Wear value in wears.Values) { Restore(value); } return; } foreach (PlayerCustomization item2 in new List(wears.Keys)) { Refresh(item2); } } public void Dispose() { if (disposed) { return; } disposed = true; foreach (Wear value in wears.Values) { Restore(value); } wears.Clear(); if (Active == this) { Active = null; } } } [Serializable] public sealed class CompletionNotice { public const string GunPurchaseTask = "Gun ready for pickup"; public const string GunPurchaseAmount = "1 ticket"; public string Session; public long Sequence; public string Task; public string Actor; public string Amount; public bool Penalty; public bool IsGunPurchase() { if (!Penalty && string.Equals(Task, "Gun ready for pickup", StringComparison.Ordinal)) { return string.Equals(Amount, "1 ticket", StringComparison.Ordinal); } return false; } } internal sealed class ChaosNpcCombat : IDisposable { private sealed class KillAttempt { public QuotaGun Gun; public NPC Npc; public NetworkConnectionToClient Connection; public string Key; public Vector3 HitPoint; public Vector3 Direction; } private sealed class CommandScope : IDisposable { public QuotaGun Gun; public NetworkConnectionToClient Connection; private CommandScope previous; private bool disposed; public CommandScope(QuotaGun gun, NetworkConnectionToClient connection) { Gun = gun; Connection = connection; previous = command; command = this; } public void Dispose() { if (!disposed) { disposed = true; command = previous; } } } [HarmonyPatch] private static class NativeQuotaCashPatch { private static bool Prepare() { return NativeQuotaAward != null; } private static MethodBase TargetMethod() { return NativeQuotaAward; } private static bool Prefix() { ChaosNpcCombat current = ChaosNpcCombat.current; if (current == null || current.disposed || !NativeSupported || !NetworkServer.active) { return true; } try { return !NpcCombatPolicy.SuppressNativeQuotaCash(ownerPresent: true, current.disposed, NativeSupported, NetworkServer.active, current.nativeQuotaOverrideAllowed()); } catch (Exception error) { current.Failure(error); return true; } } } [HarmonyPatch] private static class ShooterPatch { private static bool Prepare() { return NpcInvoker != null; } private static MethodBase TargetMethod() { return NpcInvoker; } private static void Prefix(NetworkBehaviour __0, NetworkConnectionToClient __2, out CommandScope __state) { __state = new CommandScope((QuotaGun)(object)((__0 is QuotaGun) ? __0 : null), __2); } private static void Postfix(CommandScope __state) { __state?.Dispose(); } private static Exception Finalizer(Exception __exception, CommandScope __state) { __state?.Dispose(); return __exception; } } [HarmonyPatch] private static class ShootNpcPatch { private static bool Prepare() { return NpcCommand != null; } private static MethodBase TargetMethod() { return NpcCommand; } private static bool Prefix(QuotaGun __instance, NPC __0, Vector3 __1, Vector3 __2, out KillAttempt __state) { //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) __state = null; if (!ChaosGunReload.NativeShotAllowed(__instance)) { return false; } if (current == null || current.disposed || !NativeSupported || !NetworkServer.active || !current.active) { return true; } try { if (!current.featureAllowed()) { return true; } __state = current.Prepare(__instance, __0, __1, __2); return __state != null; } catch (Exception error) { current.Failure(error); return false; } } private static void Postfix(KillAttempt __state) { if (current != null) { try { current.Complete(__state); } catch (Exception error) { current.Failure(error); } } } } [HarmonyPatch(typeof(NPC), "set_State")] private static class RecoveryPatch { private static bool Prefix(NPC __instance, NPCState __0) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Invalid comparison between Unknown and I4 if (current != null && !current.disposed && (int)__0 != 1) { return !current.corpses.ContainsKey(__instance); } return true; } } [HarmonyPatch(typeof(NPCController), "RegisterNPC")] private static class RegisterPatch { private static bool Prefix(NPC __0) { if (current != null && !current.disposed && !((Object)(object)__0 == (Object)null)) { return !current.corpses.ContainsKey(__0); } return true; } } [HarmonyPatch(typeof(NPC), "OnDestroy")] private static class DestroyPatch { private static void Prefix(NPC __instance) { if (current != null) { current.corpses.Remove(__instance); current.stableKeys.Remove(__instance); current.stableParents.Remove(__instance); current.failedCorpses.Remove(__instance); } } } [HarmonyPatch(typeof(Item), "OnDestroy")] private static class GunDestroyPatch { private static void Prefix(Item __instance) { if (current != null && __instance is QuotaGun) { current.shots.Remove(((Object)__instance).GetInstanceID()); } } } [HarmonyPatch(typeof(QuotaGun), "PlayHitEffects")] private static class BloodAssetPatch { private static void Prefix(QuotaGun __instance) { if (current != null && !current.disposed) { current.ObserveBloodPrefab(__instance); } } } private static ChaosNpcCombat current; [ThreadStatic] private static CommandScope command; private readonly Func featureAllowed; private readonly Func nativeQuotaOverrideAllowed; private readonly Func award; private readonly Action log; private readonly Action deathChanged; private readonly ChaosNpcPanic panic; private readonly HashSet killed = new HashSet(StringComparer.Ordinal); private readonly HashSet fresh = new HashSet(StringComparer.Ordinal); private readonly Dictionary corpses = new Dictionary(); private readonly Dictionary stableKeys = new Dictionary(); private readonly Dictionary stableParents = new Dictionary(); private readonly HashSet failedCorpses = new HashSet(); private readonly NpcShotGate shots = new NpcShotGate(); private readonly List blood = new List(); private string dayToken; private bool active; private bool disposed; private bool triedBloodResource; private GameObject bloodPrefab; private static readonly FieldInfo NpcsField = AccessTools.Field(typeof(NPCSpawner), "NPCs"); private static readonly FieldInfo SpawnerInstance = AccessTools.Field(typeof(NPCSpawner), "_instance"); private static readonly FieldInfo ControllerInstance = AccessTools.Field(typeof(NPCController), "_instance"); private static readonly FieldInfo RecoveryField = AccessTools.Field(typeof(NPC), "_ragdollRoutine"); private static readonly FieldInfo GunRangeField = AccessTools.Field(typeof(QuotaGun), "raycastDistance"); private static readonly FieldInfo GunMaskField = AccessTools.Field(typeof(QuotaGun), "rayMask"); private static readonly FieldInfo GunCooldownField = AccessTools.Field(typeof(QuotaGun), "shootCooldown"); private static readonly FieldInfo GunHitEffectField = AccessTools.Field(typeof(QuotaGun), "hitVFX"); private static readonly MethodInfo NpcCommand = AccessTools.Method(typeof(QuotaGun), "UserCode_CmdShootNpc__NPC__Vector3__Vector3", (Type[])null, (Type[])null); private static readonly MethodInfo NpcInvoker = AccessTools.Method(typeof(QuotaGun), "InvokeUserCode_CmdShootNpc__NPC__Vector3__Vector3", (Type[])null, (Type[])null); private static readonly MethodInfo NativeQuotaAward = AccessTools.Method(typeof(QuotaGun), "AwardQuotaFraction", (Type[])null, (Type[])null); public static bool NativeSupported { get { if (NpcsField != null && SpawnerInstance != null && ControllerInstance != null && RecoveryField != null && GunRangeField != null && GunMaskField != null && GunCooldownField != null && NpcCommand != null && NpcInvoker != null && NativeQuotaAward != null && NativeQuotaAward.ReturnType == typeof(void)) { return NativeQuotaAward.GetParameters().Length == 0; } return false; } } public ChaosNpcCombat(Func featureAllowed, Func award, Action log, Func nativeQuotaOverrideAllowed = null, Action deathChanged = null) { if (featureAllowed == null || award == null) { throw new ArgumentNullException(); } if (current != null) { throw new InvalidOperationException("NPC combat already has an owner."); } this.featureAllowed = featureAllowed; this.award = award; this.log = log; this.deathChanged = deathChanged; panic = new ChaosNpcPanic(IsDead, log); this.nativeQuotaOverrideAllowed = nativeQuotaOverrideAllowed ?? ((Func)(() => true)); current = this; } public void SetAuthoritativeDeaths(string token, string[] keys, bool enabled) { if (disposed || token == null || token.Length > 128 || keys == null || keys.Length > 512) { return; } if (NpcCombatPolicy.ClearDeathEpoch(token, keys.Length, enabled)) { ResetAuthoritativeDeaths(); } else { if (token.Length == 0) { return; } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (string text in keys) { if (!NpcCombatPolicy.ValidKey(text) || !hashSet.Add(text)) { return; } } bool flag = NpcCombatPolicy.SameDeathEpoch(dayToken, token); if (!flag) { panic.Reset(newDay: true); corpses.Clear(); stableKeys.Clear(); stableParents.Clear(); failedCorpses.Clear(); shots.Clear(); fresh.Clear(); killed.Clear(); } foreach (string item3 in hashSet) { if (flag && !killed.Contains(item3)) { fresh.Add(item3); } } if (!NetworkServer.active) { killed.Clear(); } foreach (string item4 in hashSet) { killed.Add(item4); } dayToken = token; active = enabled; if (!active) { panic.Reset(newDay: false); } } } public string[] GetAuthoritativeDeathKeys() { List list = new List(killed); list.Sort(StringComparer.Ordinal); return list.ToArray(); } public void ResetAuthoritativeDeaths() { if (!disposed) { panic.Reset(newDay: true); active = false; dayToken = null; killed.Clear(); fresh.Clear(); corpses.Clear(); stableKeys.Clear(); stableParents.Clear(); failedCorpses.Clear(); shots.Clear(); } } public NpcPanicEvent GetPanicEvent() { return panic.Snapshot; } public void SetAuthoritativePanic(NpcPanicEvent value) { if (!disposed && active) { panic.Receive(value, NativeNpcs()); } else { panic.Reset(newDay: false); } } private bool IsDead(NPC npc) { if (!((Object)(object)npc == (Object)null) && !corpses.ContainsKey(npc)) { if (stableKeys.TryGetValue(npc, out var value)) { return killed.Contains(value); } return false; } return true; } internal static bool PanicControlsNpc(NPC npc) { if (current != null && !current.disposed) { return current.panic.Controls(npc); } return false; } internal static bool ConfirmedDead(NPC npc) { if (current != null && !current.disposed) { return current.IsDead(npc); } return false; } public void Tick(bool casinoActive) { //IL_0105: 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) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0119: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) //IL_009b: Unknown result type (might be due to invalid IL or missing references) //IL_00a0: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } for (int num = blood.Count - 1; num >= 0; num--) { if ((Object)(object)blood[num] == (Object)null) { blood.RemoveAt(num); } } if (!active || !casinoActive || !NativeSupported) { panic.Reset(newDay: false); return; } SyncList val = NativeNpcs(); if (val == null || val.Count > 512) { panic.Reset(newDay: false); return; } panic.Tick(casinoActive: true, val); if (!CacheStableKeys(val)) { return; } Enumerator enumerator = val.GetEnumerator(); try { while (enumerator.MoveNext()) { NPC val2 = enumerator.Current; string text = StableKey(val2); if (text == null || !killed.Contains(text) || corpses.ContainsKey(val2) || failedCorpses.Contains(val2)) { continue; } try { MakeCorpse(val2, text); if (fresh.Remove(text)) { BloodBurst(null, ((Component)val2).transform.position + Vector3.up * 0.6f); } } catch (Exception error) { failedCorpses.Add(val2); Failure(error); } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private static SyncList NativeNpcs() { NPCSpawner val = (NPCSpawner)((SpawnerInstance == null) ? null : /*isinst with value type is only supported in some contexts*/); if (!((Object)(object)val == (Object)null) && !(NpcsField == null)) { return NpcsField.GetValue(val) as SyncList; } return null; } private string StableKey(NPC npc) { if ((Object)(object)npc == (Object)null || ((object)npc).GetType() != typeof(NPC) || (Object)(object)((Component)npc).transform.parent == (Object)null) { return null; } if (stableKeys.TryGetValue(npc, out var value)) { if (!stableParents.TryGetValue(npc, out var value2) || !((Object)(object)value2 == (Object)(object)((Component)npc).transform.parent)) { return null; } return value; } SyncList npcs = NativeNpcs(); if (!CacheStableKeys(npcs) || !stableKeys.TryGetValue(npc, out value)) { return null; } return value; } private bool CacheStableKeys(SyncList npcs) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) if (npcs == null || npcs.Count > 512) { return false; } bool flag = false; Enumerator enumerator = npcs.GetEnumerator(); try { while (enumerator.MoveNext()) { NPC val = enumerator.Current; if ((Object)(object)val == (Object)null || ((object)val).GetType() != typeof(NPC) || (Object)(object)((Component)val).transform.parent == (Object)null) { return false; } if (!stableParents.TryGetValue(val, out var value)) { flag = true; } else if ((Object)(object)value != (Object)(object)((Component)val).transform.parent) { return false; } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } if (!flag) { return true; } int[] array = new int[npcs.Count]; int[] array2 = new int[npcs.Count]; bool[] array3 = new bool[npcs.Count]; string[] array4 = new string[npcs.Count]; Transform[] array5 = (Transform[])(object)new Transform[npcs.Count]; HashSet hashSet = new HashSet(); for (int i = 0; i < npcs.Count; i++) { NPC val2 = npcs[i]; Transform parent = ((Component)val2).transform.parent; NPCHolder component = ((Component)parent).GetComponent(); CasinoFloor val3 = (((Object)(object)component == (Object)null) ? null : ((Component)component).GetComponentInParent()); if ((Object)(object)val3 == (Object)null || !hashSet.Add(val2)) { return false; } array[i] = val3.floorIndex; array2[i] = ((Object)parent).GetInstanceID(); array3[i] = true; stableKeys.TryGetValue(val2, out array4[i]); array5[i] = parent; } if (!NpcCombatPolicy.TryMapSlots(array, array2, array3, array4, out var keys)) { return false; } stableKeys.Clear(); stableParents.Clear(); for (int j = 0; j < npcs.Count; j++) { stableKeys.Add(npcs[j], keys[j]); stableParents.Add(npcs[j], array5[j]); } return true; } private KillAttempt Prepare(QuotaGun gun, NPC npc, Vector3 direction, Vector3 reportedPoint) { //IL_01d3: Unknown result type (might be due to invalid IL or missing references) //IL_01f7: Unknown result type (might be due to invalid IL or missing references) //IL_025d: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) //IL_02cd: Unknown result type (might be due to invalid IL or missing references) //IL_02a3: Unknown result type (might be due to invalid IL or missing references) //IL_02a8: Unknown result type (might be due to invalid IL or missing references) //IL_0320: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Invalid comparison between Unknown and I4 //IL_03fc: Unknown result type (might be due to invalid IL or missing references) //IL_0401: Unknown result type (might be due to invalid IL or missing references) //IL_040a: Unknown result type (might be due to invalid IL or missing references) //IL_040f: Unknown result type (might be due to invalid IL or missing references) NetworkConnectionToClient val = ((command == null || (Object)(object)command.Gun != (Object)(object)gun) ? null : command.Connection); NetworkIdentity val2 = ((val == null) ? null : ((NetworkConnection)val).identity); PlayerInventory val3 = (((Object)(object)gun == (Object)null) ? null : ((Item)gun).NetworkHolder); SyncList val4 = NativeNpcs(); string text = StableKey(npc); NetworkConnectionToClient value; bool currentConnection = val != null && NetworkServer.connections.TryGetValue(val.connectionId, out value) && object.ReferenceEquals(value, val); NetworkIdentity value2; bool ownedSpawnedCharacter = (Object)(object)val2 != (Object)null && val2.netId != 0 && object.ReferenceEquals(val2.connectionToClient, val) && NetworkServer.spawned.TryGetValue(val2.netId, out value2) && object.ReferenceEquals(value2, val2) && (Object)(object)((Component)val2).GetComponent() != (Object)null; NetworkIdentity value3; bool heldSpawnedGun = (Object)(object)gun != (Object)null && ((NetworkBehaviour)gun).netId != 0 && (Object)(object)val3 != (Object)null && (Object)(object)val2 != (Object)null && object.ReferenceEquals(((NetworkBehaviour)val3).netIdentity, val2) && (Object)(object)val3.holdingItem == (Object)(object)gun && !((Item)gun).isInPocket && NetworkServer.spawned.TryGetValue(((NetworkBehaviour)gun).netId, out value3) && object.ReferenceEquals(value3, ((NetworkBehaviour)gun).netIdentity); NetworkIdentity value4; bool registeredCasinoNpc = (Object)(object)npc != (Object)null && ((NetworkBehaviour)npc).isServer && ((Behaviour)npc).isActiveAndEnabled && ((NetworkBehaviour)npc).netId != 0 && text != null && val4 != null && val4.Count <= 512 && val4.Contains(npc) && (Object)(object)((Component)npc).GetComponent() != (Object)null && NetworkServer.spawned.TryGetValue(((NetworkBehaviour)npc).netId, out value4) && object.ReferenceEquals(value4, ((NetworkBehaviour)npc).netIdentity) && !corpses.ContainsKey(npc) && !killed.Contains(text); bool flag = Finite(direction) && ((Vector3)(ref direction)).sqrMagnitude >= 0.81f && ((Vector3)(ref direction)).sqrMagnitude <= 1.21f && Finite(reportedPoint); float num = (((Object)(object)gun == (Object)null) ? 0f : ((float)GunRangeField.GetValue(gun))); float num2 = (((Object)(object)gun == (Object)null) ? (-1f) : ((float)GunCooldownField.GetValue(gun))); PlayerController val5 = (((Object)(object)val2 == (Object)null) ? null : ((Component)val2).GetComponent()); RaycastHit hit = default(RaycastHit); bool flag2 = Finite(num) && num > 0f && num <= 250f && (Object)(object)val5 != (Object)null && (Object)(object)val5.head != (Object)null && Vector3.Distance(((Component)val5.head).transform.position, reportedPoint) <= num + 0.4f; bool flag3 = flag && flag2 && ChaosGunTargeting.TryValidateNpcShot(gun, val, npc, direction, reportedPoint, out hit); GameManager instance = NetworkSingleton.Instance; NpcShotEvidence npcShotEvidence = new NpcShotEvidence(); npcShotEvidence.HostActive = NetworkServer.active; npcShotEvidence.CompatibleRoom = active && featureAllowed(); npcShotEvidence.CasinoDay = (Object)(object)instance != (Object)null && (int)instance.state == 1 && instance.HasDayStarted; npcShotEvidence.CurrentConnection = currentConnection; npcShotEvidence.Authenticated = ((NetworkConnection)(val?)).isAuthenticated ?? false; npcShotEvidence.Ready = ((NetworkConnection)(val?)).isReady ?? false; npcShotEvidence.OwnedSpawnedCharacter = ownedSpawnedCharacter; npcShotEvidence.HeldSpawnedGun = heldSpawnedGun; npcShotEvidence.RegisteredCasinoNpc = registeredCasinoNpc; npcShotEvidence.ValidDirection = flag; npcShotEvidence.ServerRayHitsNpc = flag3; npcShotEvidence.WithinRange = flag2; npcShotEvidence.ReportedPointMatches = flag3; NpcShotEvidence proof = npcShotEvidence; if (!NpcCombatPolicy.Eligible(proof) || !shots.TryAccept(((Object)gun).GetInstanceID(), Time.time, num2)) { return null; } KillAttempt killAttempt = new KillAttempt(); killAttempt.Gun = gun; killAttempt.Npc = npc; killAttempt.Connection = val; killAttempt.Key = text; killAttempt.HitPoint = ((RaycastHit)(ref hit)).point; killAttempt.Direction = ((Vector3)(ref direction)).normalized; return killAttempt; } private void Complete(KillAttempt attempt) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Invalid comparison between Unknown and I4 //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (attempt != null && !((Object)(object)attempt.Npc == (Object)null) && NpcCombatPolicy.ConfirmDeath(validatedAttempt: true, NetworkServer.active, (int)attempt.Npc.State == 1, killed.Contains(attempt.Key))) { killed.Add(attempt.Key); fresh.Remove(attempt.Key); MakeCorpse(attempt.Npc, attempt.Key); BloodBurst(attempt.Gun, attempt.HitPoint, attempt.Direction); panic.ConfirmDeath(attempt.Npc, attempt.Key, NativeNpcs()); try { award(attempt.Connection, attempt.Key); } catch (Exception error) { Failure(error); } if (deathChanged != null) { deathChanged(); } } } private void MakeCorpse(NPC npc, string key) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Invalid comparison between Unknown and I4 if ((Object)(object)npc == (Object)null || corpses.Count >= 512) { return; } corpses[npc] = key; if (NetworkServer.active && ((NetworkBehaviour)npc).isServer) { if ((int)npc.State != 1) { npc.State = (NPCState)1; } object? value = RecoveryField.GetValue(npc); Coroutine val = (Coroutine)((value is Coroutine) ? value : null); if (val != null) { ((MonoBehaviour)npc).StopCoroutine(val); RecoveryField.SetValue(npc, null); } NPCController val2 = (NPCController)((ControllerInstance == null) ? null : /*isinst with value type is only supported in some contexts*/); NPCBehaviorState val3 = (((Object)(object)val2 == (Object)null) ? null : val2.GetNPCState(npc)); if (val3 != null && (Object)(object)val3.currentSocket != (Object)null) { try { if (val3.hasEnteredSocket && (Object)(object)val3.currentSocket.Action != (Object)null) { val3.currentSocket.Action.OnExit(npc, val3.currentSocket); } } catch (Exception error) { Failure(error); } val3.currentSocket.Release(); val3.currentSocket = null; val3.hasEnteredSocket = false; } if ((Object)(object)val2 != (Object)null) { val2.UnregisterNPC(npc); } } Animator component = ((Component)npc).GetComponent(); if ((Object)(object)component != (Object)null) { ((Behaviour)component).enabled = false; } NPCEyes component2 = ((Component)npc).GetComponent(); if ((Object)(object)component2 != (Object)null) { ((Behaviour)component2).enabled = false; } if ((Object)(object)npc.npcSfx != (Object)null) { ((Behaviour)npc.npcSfx).enabled = false; } } private void BloodBurst(QuotaGun gun, Vector3 point, Vector3 direction = default(Vector3)) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00f7: Unknown result type (might be due to invalid IL or missing references) //IL_00fc: 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_010b: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_0117: 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_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01b0: 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) //IL_01dc: Unknown result type (might be due to invalid IL or missing references) //IL_01ed: Unknown result type (might be due to invalid IL or missing references) //IL_0209: Unknown result type (might be due to invalid IL or missing references) //IL_020e: Unknown result type (might be due to invalid IL or missing references) //IL_021a: Unknown result type (might be due to invalid IL or missing references) //IL_021f: Unknown result type (might be due to invalid IL or missing references) //IL_022b: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Unknown result type (might be due to invalid IL or missing references) //IL_023c: Unknown result type (might be due to invalid IL or missing references) //IL_0241: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0252: Unknown result type (might be due to invalid IL or missing references) //IL_025e: Unknown result type (might be due to invalid IL or missing references) //IL_0263: Unknown result type (might be due to invalid IL or missing references) if (!Finite(point) || blood.Count >= 8) { return; } ObserveBloodPrefab(gun); if (!triedBloodResource && (Object)(object)bloodPrefab == (Object)null) { triedBloodResource = true; bloodPrefab = Resources.Load("blood"); } GameObject val = bloodPrefab; if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponentInChildren(true) != (Object)null || (Object)(object)val.GetComponentInChildren(true) != (Object)null || (Object)(object)val.GetComponentInChildren(true) != (Object)null) { return; } MonoBehaviour[] componentsInChildren = val.GetComponentsInChildren(true); foreach (MonoBehaviour val2 in componentsInChildren) { if ((Object)(object)val2 == (Object)null || ((object)val2).GetType() != typeof(PlayVfxOnEnable)) { return; } } ParticleSystem[] componentsInChildren2 = val.GetComponentsInChildren(true); if (componentsInChildren2.Length != 0 && componentsInChildren2.Length <= 4) { Quaternion val3 = ((((Vector3)(ref direction)).sqrMagnitude > 0.01f) ? Quaternion.LookRotation(-direction + Vector3.up * 0.35f) : Quaternion.identity); GameObject val4 = Object.Instantiate(val, point, val3); blood.Add(val4); MonoBehaviour[] componentsInChildren3 = val4.GetComponentsInChildren(true); foreach (MonoBehaviour val5 in componentsInChildren3) { ((Behaviour)val5).enabled = false; Object.Destroy((Object)(object)val5); } ParticleSystem[] componentsInChildren4 = val4.GetComponentsInChildren(true); foreach (ParticleSystem val6 in componentsInChildren4) { val6.Stop(true, (ParticleSystemStopBehavior)0); MainModule main = val6.main; ((MainModule)(ref main)).loop = false; ((MainModule)(ref main)).playOnAwake = false; ((MainModule)(ref main)).maxParticles = 96; ((MainModule)(ref main)).startLifetime = new MinMaxCurve(0.55f, 1f); ((MainModule)(ref main)).startSpeed = new MinMaxCurve(2.2f, 4.2f); ((MainModule)(ref main)).startSize = new MinMaxCurve(0.045f, 0.095f); ((MainModule)(ref main)).gravityModifier = MinMaxCurve.op_Implicit(1.2f); ((MainModule)(ref main)).simulationSpace = (ParticleSystemSimulationSpace)1; ((MainModule)(ref main)).stopAction = (ParticleSystemStopAction)0; EmissionModule emission = val6.emission; ((EmissionModule)(ref emission)).enabled = false; CollisionModule collision = val6.collision; ((CollisionModule)(ref collision)).enabled = false; TriggerModule trigger = val6.trigger; ((TriggerModule)(ref trigger)).enabled = false; SubEmittersModule subEmitters = val6.subEmitters; ((SubEmittersModule)(ref subEmitters)).enabled = false; LightsModule lights = val6.lights; ((LightsModule)(ref lights)).enabled = false; TrailModule trails = val6.trails; ((TrailModule)(ref trails)).enabled = false; ((Component)val6).gameObject.SetActive(true); val6.Emit(72); } Object.Destroy((Object)(object)val4, 2f); } } private void ObserveBloodPrefab(QuotaGun gun) { if ((Object)(object)bloodPrefab != (Object)null || (Object)(object)gun == (Object)null || GunHitEffectField == null) { return; } object? value = GunHitEffectField.GetValue(gun); GameObject val = (GameObject)((value is GameObject) ? value : null); if ((Object)(object)val == (Object)null) { return; } ParticleSystem[] componentsInChildren = val.GetComponentsInChildren(true); foreach (ParticleSystem val2 in componentsInChildren) { if (((Object)val2).name.IndexOf("blood", StringComparison.OrdinalIgnoreCase) >= 0) { bloodPrefab = ((Component)val2).gameObject; break; } } } private static bool Finite(Vector3 value) { if (Finite(value.x) && Finite(value.y)) { return Finite(value.z); } return false; } private static bool Finite(float value) { if (!float.IsNaN(value)) { return !float.IsInfinity(value); } return false; } private void Failure(Exception error) { if (log != null) { log("NPC combat held: " + error.GetType().Name + ": " + error.Message); } } public void Dispose() { if (disposed) { return; } disposed = true; active = false; panic.Dispose(); foreach (GameObject item in blood) { if ((Object)(object)item != (Object)null) { Object.Destroy((Object)(object)item); } } blood.Clear(); corpses.Clear(); stableKeys.Clear(); stableParents.Clear(); failedCorpses.Clear(); killed.Clear(); fresh.Clear(); shots.Clear(); if (current == this) { current = null; } } } } namespace ChaosNight.Core { public sealed class NpcShotEvidence { public bool HostActive; public bool CompatibleRoom; public bool CasinoDay; public bool CurrentConnection; public bool Authenticated; public bool Ready; public bool OwnedSpawnedCharacter; public bool HeldSpawnedGun; public bool RegisteredCasinoNpc; public bool ValidDirection; public bool ServerRayHitsNpc; public bool WithinRange; public bool ReportedPointMatches; } public static class NpcCombatPolicy { public const int MaximumNpcs = 512; public const int MaximumFloors = 64; public const int MaximumNpcAncestorDepth = 32; public const double MaximumCameraOffset = 0.4; public const double MaximumHitPointError = 1.0; public static bool PreferObstruction(bool colliderPresent, bool ownedCollider, double candidateDistance, double closestDistance, bool candidateNpc, bool closestNpc) { if (!colliderPresent || ownedCollider || !Finite(candidateDistance) || candidateDistance < 0.0 || double.IsNaN(closestDistance) || closestDistance < 0.0) { return false; } if (double.IsPositiveInfinity(closestDistance)) { return true; } if (!(candidateDistance < closestDistance - 0.001)) { if (Math.Abs(candidateDistance - closestDistance) <= 0.001 && closestNpc) { return !candidateNpc; } return false; } return true; } public static bool TryCameraLine(double headX, double headY, double headZ, double pointX, double pointY, double pointZ, double dirX, double dirY, double dirZ, double range, out double offsetX, out double offsetY, out double offsetZ, out double distance) { offsetX = (offsetY = (offsetZ = (distance = 0.0))); if (!Finite(headX) || !Finite(headY) || !Finite(headZ) || !Finite(pointX) || !Finite(pointY) || !Finite(pointZ) || !Finite(dirX) || !Finite(dirY) || !Finite(dirZ) || !Finite(range) || range <= 0.0 || range > 250.0) { return false; } double num = dirX * dirX + dirY * dirY + dirZ * dirZ; if (num < 0.81 || num > 1.21) { return false; } num = Math.Sqrt(num); dirX /= num; dirY /= num; dirZ /= num; double num2 = pointX - headX; double num3 = pointY - headY; double num4 = pointZ - headZ; double num5 = num2 * dirX + num3 * dirY + num4 * dirZ; if (!Finite(num5) || num5 < 0.02 || num5 > range + 0.4) { return false; } double num6 = num2 - num5 * dirX; double num7 = num3 - num5 * dirY; double num8 = num4 - num5 * dirZ; double num9 = num6 * num6 + num7 * num7 + num8 * num8; if (!Finite(num9) || num9 > 0.16000000000000003 || num2 * num2 + num3 * num3 + num4 * num4 > (range + 0.4) * (range + 0.4)) { return false; } offsetX = num6; offsetY = num7; offsetZ = num8; distance = num5; return true; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } public static bool SuppressNativeQuotaCash(bool ownerPresent, bool disposed, bool nativeSupported, bool serverActive, bool compatible) { if (ownerPresent && !disposed && nativeSupported && serverActive) { return compatible; } return false; } public static bool Eligible(NpcShotEvidence proof) { if (proof != null && proof.HostActive && proof.CompatibleRoom && proof.CasinoDay && proof.CurrentConnection && proof.Authenticated && proof.Ready && proof.OwnedSpawnedCharacter && proof.HeldSpawnedGun && proof.RegisteredCasinoNpc && proof.ValidDirection && proof.ServerRayHitsNpc && proof.WithinRange) { return proof.ReportedPointMatches; } return false; } public static bool ConfirmDeath(bool validatedAttempt, bool serverActive, bool nativeRagdoll, bool alreadyKilled) { if (validatedAttempt && serverActive && nativeRagdoll) { return !alreadyKilled; } return false; } public static bool ClearDeathEpoch(string token, int deathCount, bool enabled) { if (token != null && token.Length == 0 && deathCount == 0) { return !enabled; } return false; } public static bool SameDeathEpoch(string previous, string next) { if (!string.IsNullOrEmpty(next) && next.Length <= 128) { return string.Equals(previous, next, StringComparison.Ordinal); } return false; } public static string StableKey(int physicalFloor, int holderNpcOrdinal) { if (physicalFloor < 1 || physicalFloor > 64 || holderNpcOrdinal < 0 || holderNpcOrdinal >= 512) { return null; } return "n1/f" + physicalFloor.ToString(CultureInfo.InvariantCulture) + "/s" + holderNpcOrdinal.ToString(CultureInfo.InvariantCulture); } public static bool ValidKey(string key) { if (string.IsNullOrEmpty(key) || key.Length > 16 || !key.StartsWith("n1/f", StringComparison.Ordinal)) { return false; } int num = key.IndexOf("/s", 4, StringComparison.Ordinal); if (num < 5 || !int.TryParse(key.Substring(4, num - 4), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(key.Substring(num + 2), NumberStyles.None, CultureInfo.InvariantCulture, out var result2)) { return false; } return key == StableKey(result, result2); } public static bool TryMapSlots(int[] floors, int[] holders, bool[] configured, string[] previous, out string[] keys) { keys = null; if (floors == null || holders == null || configured == null || previous == null || floors.Length > 512 || holders.Length != floors.Length || configured.Length != floors.Length || previous.Length != floors.Length) { return false; } string[] array = new string[floors.Length]; Dictionary dictionary = new Dictionary(); HashSet hashSet = new HashSet(StringComparer.Ordinal); for (int i = 0; i < floors.Length; i++) { if (!configured[i] || holders[i] == 0) { return false; } dictionary.TryGetValue(holders[i], out var value); dictionary[holders[i]] = value + 1; string text = StableKey(floors[i], value); if (text == null || !hashSet.Add(text) || (previous[i] != null && previous[i] != text)) { return false; } array[i] = text; } keys = array; return true; } } public sealed class NpcShotGate { private readonly Dictionary lastShot = new Dictionary(); public bool TryAccept(int gunId, double hostTime, double nativeCooldown) { if (gunId == 0 || double.IsNaN(hostTime) || double.IsInfinity(hostTime) || hostTime < 0.0 || double.IsNaN(nativeCooldown) || double.IsInfinity(nativeCooldown) || nativeCooldown < 0.0 || nativeCooldown > 60.0) { return false; } if (lastShot.TryGetValue(gunId, out var value)) { if (hostTime <= value || hostTime - value < Math.Max(0.05, nativeCooldown)) { return false; } } else if (lastShot.Count >= 512) { return false; } lastShot[gunId] = hostTime; return true; } public void Remove(int gunId) { lastShot.Remove(gunId); } public void Clear() { lastShot.Clear(); } } } namespace ChaosNight { internal sealed class ChaosNpcPanic : IDisposable { private sealed class Reaction { public NavMeshAgent Agent; public float Speed; public float Acceleration; public float StoppingDistance; public bool OwnedAI; public bool ChangedAgent; public double NextMove; public double LastBubble = -1.0; public double BubbleUntil; public int BubbleLine = -1; } private readonly Func dead; private readonly Action log; private readonly Dictionary reactions = new Dictionary(); private readonly List remove = new List(); private readonly NavMeshPath path = new NavMeshPath(); private NpcPanicEvent current; private long sequence; private long receivedSequence; private CasinoFloor floor; private Vector3 threat; private ChaosNpcPanicOverlay overlay; private bool disposed; private static readonly FieldInfo ControllerInstance = AccessTools.Field(typeof(NPCController), "_instance"); private static readonly FieldInfo LocalInstance = AccessTools.Field(typeof(LocalManager), "_instance"); public NpcPanicEvent Snapshot { get { if (!NpcPanicPolicy.Active(current, NetworkTime.time)) { return null; } NpcPanicEvent npcPanicEvent = new NpcPanicEvent(); npcPanicEvent.Sequence = current.Sequence; npcPanicEvent.Victim = current.Victim; npcPanicEvent.DeathKey = current.DeathKey; npcPanicEvent.StartedAt = current.StartedAt; npcPanicEvent.EndsAt = current.EndsAt; return npcPanicEvent; } } public ChaosNpcPanic(Func dead, Action log) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Expected O, but got Unknown this.dead = dead; this.log = log; } public bool Controls(NPC npc) { return NpcPanicPolicy.RetainsControl(disposed, (Object)(object)npc != (Object)null, (Object)(object)npc != (Object)null && reactions.ContainsKey(npc)); } public void ConfirmDeath(NPC victim, string key, SyncList npcs) { if (!disposed && NetworkServer.active && !((Object)(object)victim == (Object)null) && ((NetworkBehaviour)victim).netId != 0 && NpcCombatPolicy.ValidKey(key)) { double time = NetworkTime.time; NpcPanicEvent npcPanicEvent = new NpcPanicEvent(); npcPanicEvent.Sequence = ++sequence; npcPanicEvent.Victim = ((NetworkBehaviour)victim).netId; npcPanicEvent.DeathKey = key; npcPanicEvent.StartedAt = time; npcPanicEvent.EndsAt = time + 12.0; NpcPanicEvent next = npcPanicEvent; Apply(next, npcs); } } public void Receive(NpcPanicEvent next, SyncList npcs) { if (disposed || NetworkServer.active || !NpcPanicPolicy.ValidEvent(next)) { return; } if (next == null) { Reset(newDay: false); } else if (next.Sequence > receivedSequence) { if (!NpcPanicPolicy.Active(next, NetworkTime.time)) { receivedSequence = next.Sequence; Reset(newDay: false); } else { receivedSequence = next.Sequence; Apply(next, npcs); } } } private void Apply(NpcPanicEvent next, SyncList npcs) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: 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_00ae: Expected O, but got Unknown current = next; Dictionary dictionary = (NetworkServer.active ? NetworkServer.spawned : NetworkClient.spawned); NetworkIdentity value; NPC val = ((dictionary.TryGetValue(next.Victim, out value) && (Object)(object)value != (Object)null) ? ((Component)value).GetComponent() : null); CasinoFloor val2 = (((Object)(object)val == (Object)null) ? null : ((Component)val).GetComponentInParent()); if ((Object)(object)floor != (Object)null && (Object)(object)floor != (Object)(object)val2) { ReleaseAll(); } floor = val2; if ((Object)(object)val != (Object)null) { threat = ((Component)val).transform.position; } if ((Object)(object)overlay == (Object)null) { GameObject val3 = new GameObject("Chaos NPC panic bubbles"); overlay = val3.AddComponent(); overlay.Draw = Draw; } Populate(npcs); } public void Tick(bool casinoActive, SyncList npcs) { //IL_0282: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } double time = NetworkTime.time; if (!casinoActive || !NpcPanicPolicy.Active(current, time)) { Reset(newDay: false); return; } if ((Object)(object)floor == (Object)null) { Apply(current, npcs); if ((Object)(object)floor == (Object)null) { return; } } if (!((Component)floor).gameObject.activeInHierarchy) { Reset(newDay: false); return; } Populate(npcs); remove.Clear(); int num = 40; foreach (KeyValuePair reaction2 in reactions) { NPC key = reaction2.Key; Reaction value = reaction2.Value; if (!Eligible(key)) { remove.Add(key); continue; } int num2 = NpcPanicPolicy.BubbleLine(current, ((NetworkBehaviour)key).netId, time); double num3 = NpcPanicPolicy.BubbleStartedAt(current, ((NetworkBehaviour)key).netId, time); if (num2 >= 0 && NpcPanicPolicy.CanSpeak(num3, value.LastBubble)) { value.LastBubble = num3; value.BubbleUntil = Math.Min(current.EndsAt, num3 + 2.35); value.BubbleLine = num2; } if (!NetworkServer.active || time < value.NextMove || num == 0) { continue; } num--; value.NextMove = time + 1.7 + (double)(((NetworkBehaviour)key).netId % 7) * 0.1; NavMeshAgent agent = key.Agent; if ((Object)(object)agent == (Object)null || !((Behaviour)agent).enabled || !agent.isOnNavMesh || !Destination(key, agent, out var destination)) { continue; } if (!value.ChangedAgent) { value.Agent = agent; value.Speed = agent.speed; value.Acceleration = agent.acceleration; value.StoppingDistance = agent.stoppingDistance; value.ChangedAgent = true; } if (!value.OwnedAI) { NPCController val = Controller(); NPCBehaviorState val2 = (((Object)(object)val == (Object)null) ? null : val.GetNPCState(key)); if (val2 != null) { ReleaseSocket(key, val2); val.UnregisterNPC(key); value.OwnedAI = true; } } agent.speed = Math.Max(value.Speed, 4.2f); agent.acceleration = Math.Max(value.Acceleration, 12f); agent.stoppingDistance = 0.15f; key.SetDestination(destination); } foreach (NPC item in remove) { Reaction reaction = reactions[item]; Restore(item, reaction); reactions.Remove(item); } } private void Populate(SyncList npcs) { //IL_0028: 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) if (npcs == null || npcs.Count > 512 || (Object)(object)floor == (Object)null || current == null) { return; } Enumerator enumerator = npcs.GetEnumerator(); try { while (enumerator.MoveNext()) { NPC val = enumerator.Current; if (Eligible(val) && !reactions.ContainsKey(val)) { ChaosGunTargeting.TakePanicControl(val); Reaction reaction = new Reaction(); reaction.NextMove = current.StartedAt + NpcPanicPolicy.FirstMoveDelay(current.Sequence, ((NetworkBehaviour)val).netId); Reaction reaction2 = reaction; reactions.Add(val, reaction2); if (!NetworkServer.active && (Object)(object)val.Agent != (Object)null && ((Behaviour)val.Agent).enabled && val.Agent.isOnNavMesh) { NavMeshAgent val2 = (reaction2.Agent = val.Agent); reaction2.Speed = val2.speed; reaction2.Acceleration = val2.acceleration; reaction2.StoppingDistance = val2.stoppingDistance; reaction2.ChangedAgent = true; val2.speed = Math.Max(val2.speed, 4.2f); val2.acceleration = Math.Max(val2.acceleration, 12f); val2.stoppingDistance = 0.15f; } } } } finally { ((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose(); } } private bool Eligible(NPC npc) { //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 if ((Object)(object)npc != (Object)null && ((object)npc).GetType() == typeof(NPC) && ((NetworkBehaviour)npc).netId != 0 && ((NetworkBehaviour)npc).netId != ((current != null) ? current.Victim : 0) && ((Component)npc).gameObject.activeInHierarchy && ((Behaviour)npc).isActiveAndEnabled && (int)npc.State != 1 && !dead(npc)) { return (Object)(object)((Component)npc).GetComponentInParent() == (Object)(object)floor; } return false; } private bool Destination(NPC npc, NavMeshAgent agent, out Vector3 destination) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0011: 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_0019: 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_0023: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_0088: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00d7: 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_0104: Unknown result type (might be due to invalid IL or missing references) //IL_013e: Unknown result type (might be due to invalid IL or missing references) //IL_0149: Unknown result type (might be due to invalid IL or missing references) //IL_014b: Unknown result type (might be due to invalid IL or missing references) //IL_0158: 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_0169: 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) //IL_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) destination = Vector3.zero; Vector3 position = ((Component)npc).transform.position; Vector3 val = position - threat; val.y = 0f; if (((Vector3)(ref val)).sqrMagnitude < 0.01f) { ((Vector3)(ref val))..ctor(1f, 0f, 0f); } ((Vector3)(ref val)).Normalize(); NavMeshHit val3 = default(NavMeshHit); for (int i = 0; i < 8; i++) { Vector3 val2 = Quaternion.Euler(0f, Random.Range(-85f, 85f), 0f) * val; if (!NavMesh.SamplePosition(position + val2 * Random.Range(4f, 11f), ref val3, 1.5f, agent.areaMask) || !agent.CalculatePath(((NavMeshHit)(ref val3)).position, path) || (int)path.status != 0) { continue; } bool sameHeightPath = true; Vector3[] corners = path.corners; for (int j = 0; j < corners.Length; j++) { Vector3 val4 = corners[j]; if (Math.Abs(val4.y - position.y) > 1.25f) { sameHeightPath = false; break; } } if (NpcPanicPolicy.SafeDestination(position.y, ((NavMeshHit)(ref val3)).position.y, PlanarDistance(position, threat), PlanarDistance(((NavMeshHit)(ref val3)).position, threat), PlanarDistance(position, ((NavMeshHit)(ref val3)).position), completePath: true, sameHeightPath)) { destination = ((NavMeshHit)(ref val3)).position; return true; } } return false; } private static float PlanarDistance(Vector3 a, Vector3 b) { //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) a.y = (b.y = 0f); return Vector3.Distance(a, b); } private static NPCController Controller() { if (!(ControllerInstance == null)) { object? value = ControllerInstance.GetValue(null); return (NPCController)((value is NPCController) ? value : null); } return null; } private void ReleaseSocket(NPC npc, NPCBehaviorState state) { if (state == null || (Object)(object)state.currentSocket == (Object)null) { return; } try { if (state.hasEnteredSocket && (Object)(object)state.currentSocket.Action != (Object)null) { state.currentSocket.Action.OnExit(npc, state.currentSocket); } } catch (Exception ex) { if (log != null) { log("NPC panic socket release: " + ex.GetType().Name); } } state.currentSocket.Release(); state.currentSocket = null; state.hasEnteredSocket = false; } private void Restore(NPC npc, Reaction reaction) { if ((Object)(object)npc == (Object)null || dead(npc)) { return; } NavMeshAgent agent = reaction.Agent; if (reaction.ChangedAgent && (Object)(object)agent != (Object)null) { agent.speed = reaction.Speed; agent.acceleration = reaction.Acceleration; agent.stoppingDistance = reaction.StoppingDistance; if (((Behaviour)agent).enabled && agent.isOnNavMesh) { agent.ResetPath(); agent.isStopped = false; } } if (NetworkServer.active && reaction.OwnedAI && ((NetworkBehaviour)npc).isServer) { NPCController val = Controller(); if ((Object)(object)val != (Object)null) { val.RegisterNPC(npc); } } } private void ReleaseAll() { foreach (KeyValuePair reaction in reactions) { Restore(reaction.Key, reaction.Value); } reactions.Clear(); } public void Reset(bool newDay) { ReleaseAll(); current = null; floor = null; if (newDay) { sequence = (receivedSequence = 0L); } if ((Object)(object)overlay != (Object)null) { Object.Destroy((Object)(object)((Component)overlay).gameObject); } overlay = null; } public void Dispose() { if (!disposed) { Reset(newDay: true); disposed = true; } } private void Draw() { //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_00df: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_017d: Unknown result type (might be due to invalid IL or missing references) if (disposed || current == null || !NpcPanicPolicy.Active(current, NetworkTime.time)) { return; } LocalManager val = (LocalManager)((LocalInstance == null) ? null : /*isinst with value type is only supported in some contexts*/); Camera val2 = (((Object)(object)val == (Object)null) ? null : val.mainCamera); if ((Object)(object)val2 == (Object)null || (Object)(object)overlay == (Object)null) { return; } int num = 0; foreach (KeyValuePair reaction in reactions) { NPC key = reaction.Key; Reaction value = reaction.Value; if (!Eligible(key) || value.BubbleLine < 0 || NetworkTime.time >= value.BubbleUntil) { continue; } Vector3 val3 = val2.WorldToScreenPoint(((Component)key).transform.position + Vector3.up * 2.1f); if (!(val3.z <= 0f) && !(val3.z > 40f) && !(val3.x < 0f) && !(val3.x > (float)Screen.width) && !(val3.y < 0f) && !(val3.y > (float)Screen.height)) { overlay.Bubble(new Rect(val3.x - 145f, (float)Screen.height - val3.y - 42f, 290f, 38f), NpcPanicPolicy.Lines[value.BubbleLine]); if (++num >= 24) { break; } } } } } internal sealed class ChaosNpcPanicOverlay : MonoBehaviour { public Action Draw; private GUIStyle bubble; private Texture2D background; private void OnGUI() { if (Draw != null) { Draw(); } } public void Bubble(Rect rect, string text) { //IL_00c6: 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) //IL_0018: Expected O, but got Unknown //IL_0034: 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_005a: Expected O, but got Unknown //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_00bc: Unknown result type (might be due to invalid IL or missing references) if (bubble == null) { background = new Texture2D(1, 1); background.SetPixel(0, 0, new Color(0.08f, 0.07f, 0.035f, 0.9f)); background.Apply(); GUIStyle val = new GUIStyle(GUI.skin.box); val.alignment = (TextAnchor)4; val.fontSize = 14; val.fontStyle = (FontStyle)1; val.wordWrap = true; val.padding = new RectOffset(8, 8, 4, 4); bubble = val; bubble.normal.background = background; bubble.normal.textColor = new Color(1f, 0.92f, 0.67f); } GUI.Box(rect, text, bubble); } private void OnDestroy() { Draw = null; if ((Object)(object)background != (Object)null) { Object.Destroy((Object)(object)background); } } } } namespace ChaosNight.Core { public sealed class NpcPanicEvent { public long Sequence; public uint Victim; public string DeathKey; public double StartedAt; public double EndsAt; } public static class NpcPanicPolicy { public const double Duration = 12.0; public const double BubbleDuration = 2.35; public const double BubbleInterval = 4.8; public const int MaximumVisibleBubbles = 24; public static readonly string[] Lines = new string[6] { "HE'S GOT A GUN!", "RUN", "PLEASE SAVE ME", "IM TOO YOUNG TO DIE", "PLEASE HELP ME", "I JUST WANT TO LOSE ALL MY MONEY" }; public static bool ValidEvent(NpcPanicEvent value) { if (value != null) { if (value.Sequence > 0 && value.Victim != 0 && NpcCombatPolicy.ValidKey(value.DeathKey) && Finite(value.StartedAt) && Finite(value.EndsAt) && value.StartedAt >= 0.0 && value.EndsAt > value.StartedAt) { return value.EndsAt - value.StartedAt <= 12.0; } return false; } return true; } public static bool Active(NpcPanicEvent value, double now) { if (value != null && ValidEvent(value) && Finite(now) && now >= value.StartedAt - 2.0) { return now < value.EndsAt; } return false; } public static bool RetainsControl(bool disposed, bool npcPresent, bool reactionRecorded) { if (!disposed && npcPresent) { return reactionRecorded; } return false; } public static int BubbleLine(NpcPanicEvent value, uint npcId, double now) { if (!Active(value, now) || npcId == 0 || npcId == value.Victim) { return -1; } uint num = Mix((uint)((int)value.Sequence ^ ((int)npcId * -1640531535))); double num2 = value.StartedAt + 0.25 + (double)(num % 2800) / 1000.0; double num3 = now - num2; if (num3 < 0.0) { return -1; } int num4 = (int)(num3 / 4.8); if (num3 - (double)num4 * 4.8 >= 2.35) { return -1; } uint num5 = Mix(num ^ (uint)((num4 + 1) * -2048144777)); if (num5 % 100 >= 55) { return -1; } return (int)(num5 / 100 % (uint)Lines.Length); } public static double BubbleStartedAt(NpcPanicEvent value, uint npcId, double now) { if (BubbleLine(value, npcId, now) < 0) { return -1.0; } uint num = Mix((uint)((int)value.Sequence ^ ((int)npcId * -1640531535))); double num2 = value.StartedAt + 0.25 + (double)(num % 2800) / 1000.0; return num2 + Math.Floor((now - num2) / 4.8) * 4.8; } public static bool CanSpeak(double started, double previous) { if (Finite(started) && started >= 0.0) { if (!(previous < 0.0)) { if (Finite(previous)) { return started - previous + 1E-06 >= 4.8; } return false; } return true; } return false; } public static double FirstMoveDelay(long sequence, uint npcId) { return 0.1 + (double)(Mix((uint)(int)sequence ^ npcId) % 1400) / 1000.0; } public static bool SafeDestination(double sourceY, double targetY, double sourceThreatDistance, double targetThreatDistance, double travelDistance, bool completePath, bool sameHeightPath) { if (Finite(sourceY) && Finite(targetY) && Finite(sourceThreatDistance) && Finite(targetThreatDistance) && Finite(travelDistance) && completePath && sameHeightPath && Math.Abs(targetY - sourceY) <= 1.25 && travelDistance >= 2.0 && travelDistance <= 16.0) { return targetThreatDistance >= sourceThreatDistance + 1.0; } return false; } private static uint Mix(uint value) { value ^= value >> 16; value *= 2246822519u; value ^= value >> 13; value *= 3266489917u; return value ^ (value >> 16); } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } } namespace ChaosNight { internal static class PoliceBailCash { internal static bool Usable(BigNumber value) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //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 (value >= BigNumber.Zero && value <= BigNumber.MaxValue && !double.IsNaN(value.Mantissa)) { return !double.IsInfinity(value.Mantissa); } return false; } internal static BigNumber Fee(BigNumber original) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) return original / 10; } internal static BigNumber Remaining(BigNumber original) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Unknown result type (might be due to invalid IL or missing references) return original - Fee(original); } internal static SaveData Prepare(string json, SaveData current, int day, BigNumber original) { //IL_0039: 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) SaveData val = JsonUtility.FromJson(json); if (val == null || current == null || val.saveName != current.saveName || val.seed != current.seed || val.daysPassed + 1 != day || !Usable(original)) { throw new InvalidDataException("Bail starting progress does not match this saved day."); } val.SetMoney(Remaining(original)); if (val.GetMoney() != Remaining(original)) { throw new InvalidDataException("Native bail cash did not retain its exact value."); } return val; } } [HarmonyPatch(typeof(InputEvents), "UpdateInteract")] internal static class PoliceBailInteractPatch { [HarmonyPriority(800)] private static bool Prefix(bool __0) { if (__0 && !((Object)(object)Plugin.Instance == (Object)null)) { return !Plugin.Instance.PoliceBailBlocksInput; } return true; } } [HarmonyPatch] internal static class PoliceBailTimerHoldPatch { private static MethodBase TargetMethod() { return ChaosTimerReturn.TimerAdvance(); } [HarmonyPriority(800)] private static bool Prefix(object __instance, ref bool __result) { Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null || !instance.PoliceBailHoldsProgress || __instance == null) { return true; } FieldInfo fieldInfo = AccessTools.Field(__instance.GetType(), "<>4__this"); GameManager val = (GameManager)((fieldInfo == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val == (Object)null || !object.ReferenceEquals(val, NetworkSingleton.Instance) || !((NetworkBehaviour)val).isServer) { return true; } FieldInfo fieldInfo2 = AccessTools.Field(__instance.GetType(), "<>2__current"); if (fieldInfo2 != null) { fieldInfo2.SetValue(__instance, null); } __result = true; return false; } } internal sealed class ChaosPoliceBailHud : IDisposable { private readonly GameObject root; private readonly Text title; private readonly Text amount; private readonly Text original; private readonly Text remaining; private readonly Text status; private readonly Text footer; private readonly Image progress; private readonly RectTransform panel; internal ChaosPoliceBailHud(Transform parent) { //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Expected O, but got Unknown //IL_0098: 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_00dd: 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) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) //IL_00fe: 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_0152: Unknown result type (might be due to invalid IL or missing references) //IL_0157: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_015f: Unknown result type (might be due to invalid IL or missing references) //IL_0176: Unknown result type (might be due to invalid IL or missing references) //IL_019f: Unknown result type (might be due to invalid IL or missing references) //IL_01b5: Unknown result type (might be due to invalid IL or missing references) //IL_01c0: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_027c: Unknown result type (might be due to invalid IL or missing references) //IL_02ff: Unknown result type (might be due to invalid IL or missing references) //IL_0319: Unknown result type (might be due to invalid IL or missing references) //IL_031e: Unknown result type (might be due to invalid IL or missing references) //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_0326: Unknown result type (might be due to invalid IL or missing references) //IL_0339: Unknown result type (might be due to invalid IL or missing references) //IL_034f: Unknown result type (might be due to invalid IL or missing references) //IL_0365: Unknown result type (might be due to invalid IL or missing references) //IL_038a: Unknown result type (might be due to invalid IL or missing references) //IL_0398: Unknown result type (might be due to invalid IL or missing references) //IL_03a4: Unknown result type (might be due to invalid IL or missing references) //IL_03b0: Unknown result type (might be due to invalid IL or missing references) //IL_03bc: Unknown result type (might be due to invalid IL or missing references) root = new GameObject("ChaosNight_BailReceipt", new Type[3] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler) }); root.transform.SetParent(parent, false); Canvas component = root.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 32760; CanvasScaler component2 = root.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1280f, 720f); component2.matchWidthOrHeight = 0.5f; RectTransform val = Box("Backdrop", root.transform, new Color(0.015f, 0.02f, 0.03f, 0.88f)); val.anchorMin = Vector2.zero; val.anchorMax = Vector2.one; val.offsetMin = Vector2.zero; val.offsetMax = Vector2.zero; panel = Box("Receipt", root.transform, new Color(0.065f, 0.08f, 0.105f, 1f)); panel.anchorMin = (panel.anchorMax = new Vector2(0.5f, 0.5f)); panel.sizeDelta = new Vector2(720f, 530f); RectTransform val3 = Box("Accent", (Transform)(object)panel, new Color(0.2f, 0.7f, 1f, 1f)); val3.anchorMin = new Vector2(0f, 1f); val3.anchorMax = Vector2.one; val3.pivot = new Vector2(0.5f, 1f); val3.sizeDelta = new Vector2(0f, 5f); title = Label((Transform)(object)panel, 32, (FontStyle)1, 40f, "Bail receipt"); Label((Transform)(object)panel, 20, (FontStyle)1, 93f, "DEGENERATE GAMBLER BAIL FEE"); Label((Transform)(object)panel, 17, (FontStyle)0, 123f, "10% of your original day-start cash"); amount = Label((Transform)(object)panel, 60, (FontStyle)1, 140f, ""); ((Graphic)amount).color = new Color(0.35f, 0.78f, 1f, 1f); original = Label((Transform)(object)panel, 22, (FontStyle)0, 235f, ""); remaining = Label((Transform)(object)panel, 25, (FontStyle)1, 278f, ""); status = Label((Transform)(object)panel, 21, (FontStyle)0, 347f, ""); RectTransform val4 = Box("CrewProgress", (Transform)(object)panel, new Color(0.15f, 0.19f, 0.25f, 1f)); val4.anchorMin = (val4.anchorMax = new Vector2(0.5f, 1f)); val4.pivot = new Vector2(0.5f, 1f); val4.anchoredPosition = new Vector2(0f, -404f); val4.sizeDelta = new Vector2(620f, 8f); RectTransform val6 = Box("Progress", (Transform)(object)val4, new Color(0.2f, 0.7f, 1f, 1f)); val6.anchorMin = Vector2.zero; val6.anchorMax = Vector2.one; val6.offsetMin = Vector2.zero; val6.offsetMax = Vector2.zero; progress = ((Component)val6).GetComponent(); progress.type = (Type)3; progress.fillMethod = (FillMethod)0; progress.fillOrigin = 0; footer = Label((Transform)(object)panel, 22, (FontStyle)1, 444f, ""); root.SetActive(false); } private static RectTransform Box(string name, Transform parent, Color color) { //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown //IL_003e: 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_0057: Expected O, but got Unknown GameObject val = new GameObject(name, new Type[2] { typeof(RectTransform), typeof(Image) }); val.transform.SetParent(parent, false); Image component = val.GetComponent(); ((Graphic)component).color = color; ((Graphic)component).raycastTarget = false; return (RectTransform)val.transform; } private static Text Label(Transform parent, int size, FontStyle style, float top, string value) { //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected O, but got Unknown //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_009f: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: 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) GameObject val = new GameObject("ReceiptText", new Type[2] { typeof(RectTransform), typeof(Text) }); val.transform.SetParent(parent, false); RectTransform val2 = (RectTransform)val.transform; val2.anchorMin = new Vector2(0f, 1f); val2.anchorMax = Vector2.one; val2.pivot = new Vector2(0.5f, 1f); val2.anchoredPosition = new Vector2(0f, 0f - top); val2.sizeDelta = new Vector2(-72f, (float)((size >= 50) ? 80 : 58)); Text component = val.GetComponent(); component.font = ReceiptFont(); component.fontSize = size; component.fontStyle = style; component.alignment = (TextAnchor)1; ((Graphic)component).color = new Color(0.92f, 0.95f, 0.99f, 1f); ((Graphic)component).raycastTarget = false; component.text = value; component.horizontalOverflow = (HorizontalWrapMode)0; component.verticalOverflow = (VerticalWrapMode)0; return component; } private static Font ReceiptFont() { try { Font builtinResource = Resources.GetBuiltinResource("LegacyRuntime.ttf"); if ((Object)(object)builtinResource != (Object)null) { return builtinResource; } } catch { } return Resources.GetBuiltinResource("Arial.ttf"); } internal void Present(UiState view, ulong localPlayer) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_0040: Expected O, but got Unknown //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: 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_0184: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) bool flag = view != null && view.PoliceBailVisible && view.PoliceBailCrew != null && localPlayer != 0; root.SetActive(flag); if (flag) { RectTransform val = (RectTransform)root.transform; Rect rect = val.rect; float num = Mathf.Max(1f, ((Rect)(ref rect)).width - 32f) / 720f; Rect rect2 = val.rect; float num2 = Mathf.Min(1f, Mathf.Min(num, Mathf.Max(1f, ((Rect)(ref rect2)).height - 32f) / 530f)); ((Transform)panel).localScale = new Vector3(num2, num2, 1f); title.text = "Bail receipt for day " + view.PoliceBailDay; amount.text = view.PoliceBailFee; original.text = "Original shared cash: " + view.PoliceBailOriginal; remaining.text = "Shared cash after bail: " + view.PoliceBailRemaining; status.text = view.PoliceBailStatus; int num3 = ((view.PoliceBailContinued != null) ? view.PoliceBailContinued.Length : 0) + ((view.PoliceBailWaived != null) ? view.PoliceBailWaived.Length : 0); progress.fillAmount = ((view.PoliceBailCrew.Length == 0) ? 0f : ((float)num3 / (float)view.PoliceBailCrew.Length)); ((RectTransform)((Component)progress).transform).anchorMax = new Vector2(progress.fillAmount, 1f); bool flag2 = view.PoliceBailContinued != null && Array.IndexOf(view.PoliceBailContinued, localPlayer) >= 0; bool flag3 = Array.IndexOf(view.PoliceBailCrew, localPlayer) >= 0; footer.text = ((!view.PoliceBailCanContinue) ? "Please wait" : ((flag2 || !flag3) ? ("Waiting for the crew (" + num3 + "/" + view.PoliceBailCrew.Length + ")") : ("Press E to continue · " + num3 + "/" + view.PoliceBailCrew.Length + " ready"))); } } public void Dispose() { if ((Object)(object)root != (Object)null) { Object.Destroy((Object)(object)root); } } } [Serializable] public sealed class PoliceBailIncidentRecord { public string Generation; public ulong Player; public int Incident; } [Serializable] public sealed class PoliceBailJournalState { public int Schema = 1; public string SaveName; public int Seed; public int Day; public string ChapterKey = "native"; public string RunEpoch; public string RestartNativeJson; public string RestartNativeHash; public double OriginalCashMantissa; public long OriginalCashExponent; public string FinancialDayKey; public string OriginalCashKey; public string ReceiptGeneration; public long LastRequestId; public long LastRevision; public PoliceBailSnapshot Receipt; public PoliceBailIncidentRecord[] Incidents = new PoliceBailIncidentRecord[0]; } [Serializable] public sealed class PoliceBailJournalFile { public int Schema = 1; public string Payload; public string Sha256; } public sealed class PoliceBailJournal { public const int MaximumBytes = 393216; private readonly string directory; public PoliceBailJournal(string root) { directory = Path.Combine(root, "ChaosNight", "bail"); } public string PathFor(string saveName, int seed) { if (string.IsNullOrWhiteSpace(saveName) || saveName != Path.GetFileName(saveName) || saveName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0) { throw new InvalidDataException("Invalid bail save identity."); } return Path.Combine(directory, ChaosSave.Hash(saveName + "/" + seed.ToString(CultureInfo.InvariantCulture)) + ".json"); } public PoliceBailJournalState Load(string saveName, int seed) { string text = PathFor(saveName, seed); if (!File.Exists(text)) { return null; } if (new FileInfo(text).Length > 393216) { throw new InvalidDataException("Bail journal exceeds its limit."); } PoliceBailJournalFile policeBailJournalFile = ChaosJson.Read(File.ReadAllText(text)); if (policeBailJournalFile == null || policeBailJournalFile.Schema != 1 || string.IsNullOrEmpty(policeBailJournalFile.Payload) || policeBailJournalFile.Sha256 != ChaosSave.Hash(policeBailJournalFile.Payload)) { throw new InvalidDataException("Bail journal integrity failed."); } PoliceBailJournalState policeBailJournalState = ChaosJson.Read(policeBailJournalFile.Payload); if (policeBailJournalState == null || policeBailJournalState.Schema != 1 || policeBailJournalState.SaveName != saveName || policeBailJournalState.Seed != seed) { throw new InvalidDataException("Bail journal belongs to another save."); } return policeBailJournalState; } public void Write(PoliceBailJournalState state) { if (state == null || state.Schema != 1) { throw new InvalidDataException("Invalid bail journal state."); } string text = PathFor(state.SaveName, state.Seed); string text2 = ChaosJson.Write(state); PoliceBailJournalFile policeBailJournalFile = new PoliceBailJournalFile(); policeBailJournalFile.Payload = text2; policeBailJournalFile.Sha256 = ChaosSave.Hash(text2); string s = ChaosJson.Write(policeBailJournalFile); byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(s); if (bytes.Length > 393216) { throw new InvalidDataException("Bail journal exceeds its limit."); } Directory.CreateDirectory(directory); string text3 = Path.Combine(directory, Guid.NewGuid().ToString("N") + ".pending"); using (FileStream fileStream = new FileStream(text3, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (ChaosSave.HashBytes(File.ReadAllBytes(text3)) != ChaosSave.HashBytes(bytes)) { throw new IOException("Bail journal staging readback failed."); } if (File.Exists(text)) { File.Replace(text3, text, text + ".previous"); } else { File.Move(text3, text); } if (ChaosSave.HashBytes(File.ReadAllBytes(text)) != ChaosSave.HashBytes(bytes)) { throw new IOException("Bail journal readback failed."); } } public Action CaptureRecovery(string saveName, int seed) { string path = PathFor(saveName, seed); byte[] before = (File.Exists(path) ? File.ReadAllBytes(path) : null); if (before != null && before.Length > 393216) { throw new InvalidDataException("Bail recovery journal exceeds its limit."); } return delegate { if (before == null) { if (File.Exists(path)) { File.Move(path, path + ".failed-" + Guid.NewGuid().ToString("N").Substring(0, 8)); } } else { Directory.CreateDirectory(directory); string text = Path.Combine(directory, Guid.NewGuid().ToString("N") + ".recovery"); using (FileStream fileStream = new FileStream(text, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(before, 0, before.Length); fileStream.Flush(flushToDisk: true); } if (ChaosSave.HashBytes(File.ReadAllBytes(text)) != ChaosSave.HashBytes(before)) { throw new IOException("Bail recovery staging failed readback."); } if (File.Exists(path)) { File.Replace(text, path, path + ".previous"); } else { File.Move(text, path); } if (ChaosSave.HashBytes(File.ReadAllBytes(path)) != ChaosSave.HashBytes(before)) { throw new IOException("Bail recovery readback failed."); } } }; } } } namespace ChaosNight.Core { public static class ChaosPresentation { public struct HudBounds { public double X; public double Y; public double Width; public double MaximumHeight; public bool Visible; } public static HudBounds GetCompactHudBounds(double canvasWidth, double canvasHeight, double nativeUpgradesBottom = -1.0) { HudBounds result = default(HudBounds); if (double.IsNaN(canvasWidth) || double.IsInfinity(canvasWidth) || canvasWidth <= 0.0 || double.IsNaN(canvasHeight) || double.IsInfinity(canvasHeight) || canvasHeight <= 0.0 || double.IsNaN(nativeUpgradesBottom) || double.IsInfinity(nativeUpgradesBottom)) { return result; } result.X = canvasWidth * 0.011; result.Y = canvasHeight * 0.304; result.Width = canvasWidth * 0.167; double num = canvasHeight * 0.768; if (nativeUpgradesBottom >= 0.0) { result.Y = Math.Max(result.Y, nativeUpgradesBottom + 8.0); } result.MaximumHeight = Math.Min(canvasHeight * 0.464, num - result.Y); result.Visible = result.Width >= 180.0 && result.MaximumHeight >= 260.0 && result.X + result.Width <= canvasWidth && result.Y >= 0.0; return result; } public static string CompactHudLines(string value, int charactersPerLine, int maximumLines) { if (string.IsNullOrEmpty(value)) { return ""; } charactersPerLine = Math.Max(16, Math.Min(160, charactersPerLine)); maximumLines = Math.Max(1, Math.Min(3, maximumLines)); string[] array = value.Replace("\r", "").Split('\n'); List list = new List(); for (int i = 0; i < array.Length; i++) { if (list.Count >= maximumLines) { break; } string text = array[i].Trim(); if (text.Length == 0) { continue; } if (text.Length > charactersPerLine) { int num = (charactersPerLine - 3) / 2; int num2 = charactersPerLine - 3 - num; int num3 = text.Length - num; if (num2 > 0 && char.IsHighSurrogate(text[num2 - 1])) { num2--; } if (num3 < text.Length && char.IsLowSurrogate(text[num3])) { num3++; } text = text.Substring(0, num2) + "..." + text.Substring(num3); } list.Add(text); } return string.Join("\n", list.ToArray()); } public static string FormatTicketAmount(long startingTickets, double ratio) { if (startingTickets <= 0 || double.IsNaN(ratio) || double.IsInfinity(ratio) || ratio <= 0.0) { return "0 tickets"; } double num = (double)startingTickets * ratio; double num2 = Math.Round(num); if (Math.Abs(num - num2) <= 1E-07) { num = num2; } num = Math.Floor(num); return num.ToString("0", CultureInfo.InvariantCulture) + ((num == 1.0) ? " ticket" : " tickets"); } public static string Modifiers(RunState s, Func cash, Func game = null, Func tickets = null) { if (s == null || s.ModifierIds == null || s.ModifierIds.Count == 0) { return "No modifiers today."; } List list = new List(); foreach (string modifierId in s.ModifierIds) { list.Add(Modifier(s, modifierId, cash, game, tickets)); } return string.Join("\n\n", list.ToArray()); } public static string Negatives(RunState s, Func cash, Func game = null) { if (s == null || s.Negatives == null || s.Negatives.Count == 0) { return ""; } List list = new List(); double num = ((s.ActiveRules != null && s.ActiveRules.PenaltySeverity == 1) ? 0.5 : ((s.ActiveRules != null && s.ActiveRules.PenaltySeverity == 3) ? 1.5 : 1.0)); double num2 = Cap(s) * num; double val = Math.Max(0.0, num2 - s.PenaltyReserved); foreach (NegativeProgress negative in s.Negatives) { if (negative == null) { continue; } string text = negative.Id ?? ""; string text2 = (text.StartsWith("timed_", StringComparison.Ordinal) ? text.Substring(6) : text); int num3 = Math.Max(1, negative.Required); int num4 = Math.Min(num3, Math.Max(0, negative.Count)); string value; string value2; switch (text2) { case "participation": { int num5 = ((negative.Roster != null) ? negative.Roster.Length : 0); value = ((num5 <= 1) ? ("Finish " + num3 + " casino games.") : ("All " + num5 + " starting players finish 1 casino game each.")); value2 = ((num5 <= 1) ? "Games: " : "Players: ") + num4 + "/" + num3; break; } case "variety": value = "Play " + num3 + " different game types."; value2 = "Game types: " + num4 + "/" + num3; break; case "net_profit": value = "Make " + Money(cash, negative.ProfitTarget) + " casino profit after losses."; value2 = "Profit: " + Money(cash, negative.Profit) + "/" + Money(cash, negative.ProfitTarget); break; case "six_plays": value = "Finish " + num3 + " casino games as a crew."; value2 = "Games: " + num4 + "/" + num3; break; default: value = (string.IsNullOrEmpty(negative.Description) ? "Finish this task." : negative.Description); value2 = "Progress: " + num4 + "/" + num3; break; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(string.IsNullOrEmpty(negative.Name) ? Name(Engine.NegativeModifiers, text) : negative.Name); stringBuilder.Append("\n").Append(value).Append("\n") .Append(value2); if (negative.Completed) { stringBuilder.Append("\nComplete. No cash lost."); } else if (negative.Failed) { stringBuilder.Append("\n").Append(PenaltyOutcome(s, negative, cash)); } else { if (!negative.Timed) { stringBuilder.Append("\nDeadline: day end"); } else if (!Finite(negative.DeadlineSeconds) || !Finite(negative.RemainingSeconds) || negative.RemainingSeconds < 0.0) { stringBuilder.Append("\nDeadline: time remaining unavailable"); } else if (negative.DeadlineSeconds <= 0.0) { stringBuilder.Append("\nDeadline: starts with the casino clock"); } else { stringBuilder.Append("\nDeadline: ").Append(Countdown(negative.RemainingSeconds)).Append(" left"); } stringBuilder.Append("\nMiss it: lose up to ").Append(Money(cash, Math.Min(Math.Max(0.0, negative.RiskRatio), val))).Append(" shared cash."); } list.Add(stringBuilder.ToString()); } if (list.Count == 0) { return ""; } return string.Join("\n\n", list.ToArray()); } public static string RiskLimits(RunState s, Func cash) { if (s == null || s.Negatives == null || s.Negatives.Count == 0) { return ""; } double num = ((s.ActiveRules != null && s.ActiveRules.PenaltySeverity == 1) ? 0.5 : ((s.ActiveRules != null && s.ActiveRules.PenaltySeverity == 3) ? 1.5 : 1.0)); return "Finish these tasks to protect shared cash; they pay no bonus. Today's loss cap: " + Money(cash, Cap(s) * num) + ". Shared cash cannot fall below $0."; } public static string NegativeHud(RunState s) { if (s == null || s.Negatives == null || s.Negatives.Count == 0) { return ""; } int num = 0; int num2 = 0; foreach (NegativeProgress negative in s.Negatives) { if (negative != null) { if (negative.Failed) { num2++; } else if (!negative.Completed) { num++; } } } if (num > 0) { return "Cash at risk: " + num + ((num == 1) ? " task left" : " tasks left") + " | Tab details"; } if (num2 <= 0) { return "Cash safe: all risk tasks done"; } return "Cash at risk: " + num2 + ((num2 == 1) ? " task missed" : " tasks missed") + " | Tab details"; } public static string HudModifiers(RunState s, Func cash, Func game = null, Func tickets = null) { if (s == null || s.ModifierIds == null || s.ModifierIds.Count == 0) { return "No modifiers today."; } List list = new List(); foreach (string modifierId in s.ModifierIds) { list.Add(string.Concat(str2: modifierId switch { "house_match" => "+20% profit; target " + Money(cash, s.QuotaFactor), "closing_time" => "20% less time; target " + Money(cash, s.QuotaFactor), "pressure_cooker" => "Target " + Money(cash, s.QuotaFactor) + "; " + TicketReward(s, 1), "breather" => "Lower target: " + Money(cash, s.QuotaFactor), "hot_table" => ShortGame(game, s.FeaturedGame) + ": +25% profit", "variety_show" => "Win game types: " + (s.VarietyCompleted ? AvailableCount(s) : Math.Min(AvailableCount(s), GameCount(s, wins: true))) + "/" + AvailableCount(s) + "; " + HudObjectiveCash(s, s.VarietyCompleted, 0.06, "Variety Show complete", cash), "crew_relay" => HudCrewProgress(s, wins: false) + "; " + HudObjectiveCash(s, s.RelayCompleted, 0.05, "Crew Relay complete", cash), "risk_dividend" => "Bets " + Money(cash, 0.03) + "+: " + (s.RiskDividendCompleted ? 3 : Math.Min(3, s.HighRollerPlays)) + "/3; " + (s.RiskDividendCompleted ? "complete" : TicketReward(s, 1)), "lucky_rotation" => ShortGame(game, s.FeaturedGame) + ": +20% profit", "rebate_night" => "10% of losses back; " + HudCash(s, Remaining(s), cash), "comeback_night" => s.ComebackCompleted ? HudObjectiveCash(s, complete: true, 0.06, "Comeback Night complete", cash) : (s.HadSetback ? ("Win back " + Money(cash, Math.Max(0.0, s.LowBalance + 0.1 - s.LastBalance)) + "; " + HudCash(s, 0.06, cash)) : ("Cash drops " + Money(cash, 0.1) + ": win it back")), "streak_night" => "Win streak: " + (s.StreakCompleted ? 3 : Math.Min(3, s.WinningStreak)) + "/3; " + HudObjectiveCash(s, s.StreakCompleted, 0.05, "Streak Night complete", cash), "ticket_rush" => "Meet target; " + TicketReward(s, 2), "frugal_friends" => "Shop " + ((tickets == null) ? ((s.Purchases * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "%") : tickets(s.Purchases)) + "/" + TicketBudget(tickets) + "; meet target", "plain_luck" => "Perks paused; meet target for a new perk", "double_trouble" => "Meet target; " + TicketReward(s, 1), "bet_it_all" => s.BetItAllUsed ? "Crew all-in used today" : "1 crew all-in available: all shared cash", _ => "Tab for details", }, str0: Name(Engine.Modifiers, modifierId), str1: ": ")); } return string.Join("\n", list.ToArray()); } public static bool HasUnresolvedRisk(RunState s) { if (s == null || !s.DayOpen || !s.DayEnabled || s.Negatives == null) { return false; } foreach (NegativeProgress negative in s.Negatives) { if (negative != null && !negative.Completed && !negative.Failed) { return true; } } return false; } public static string HudRisk(RunState s, Func cash) { if (s == null || s.Negatives == null || s.Negatives.Count == 0) { return "No cash-risk task today."; } List list = new List(); double num = ((s.ActiveRules != null && s.ActiveRules.PenaltySeverity == 1) ? 0.5 : ((s.ActiveRules != null && s.ActiveRules.PenaltySeverity == 3) ? 1.5 : 1.0)); double val = Math.Max(0.0, Cap(s) * num - s.PenaltyReserved); foreach (NegativeProgress negative in s.Negatives) { if (negative != null) { string text = HudRiskProgress(negative, cash); if (negative.Completed) { list.Add(text + "; cash safe"); continue; } if (negative.Failed) { list.Add(text + "; " + HudPenaltyOutcome(s, negative, cash)); continue; } string text2 = ((!negative.Timed) ? "by day end" : ((!Finite(negative.DeadlineSeconds) || !Finite(negative.RemainingSeconds) || negative.RemainingSeconds < 0.0) ? "time remaining unavailable" : ((negative.DeadlineSeconds <= 0.0) ? "starts with casino clock" : (Countdown(negative.RemainingSeconds) + " left")))); list.Add(text + "; lose up to " + Money(cash, Math.Min(Math.Max(0.0, negative.RiskRatio), val)) + "; " + text2); } } if (list.Count != 0) { return string.Join("\n", list.ToArray()); } return "No cash-risk task today."; } public static string HudGoal(RunState s, Func cash, Func game = null, Func tickets = null) { if (s == null || string.IsNullOrEmpty(s.ContractId)) { return "No bonus goal today."; } string text = Name(Engine.Contracts, s.ContractId); if (s.ContractCompleted) { return text + ": complete\n" + HudObjectiveCash(s, complete: true, ContractCash(s), text + " complete", cash); } string text2; switch (s.ContractId) { case "casino_tour": text2 = "Play game types: " + Math.Min(AvailableCount(s), GameCount(s, wins: false)) + "/" + AvailableCount(s); break; case "everybody_in": text2 = HudCrewProgress(s, wins: false); break; case "profit_ladder": text2 = "Profit: " + Money(cash, s.NativeProfit) + "/" + Money(cash, s.ContractTarget); break; case "recovery_crew": text2 = (s.ComebackCompleted ? "Recovery complete" : (s.HadSetback ? ("Win back " + Money(cash, Math.Max(0.0, s.LowBalance + 0.1 - s.LastBalance)) + " more") : ("Cash drops " + Money(cash, 0.1) + ": win it back"))); break; case "budget_night": if (s.Purchases > 0.35) { return text + ": shop budget exceeded\nMissed today."; } text2 = "Shop " + ((tickets == null) ? ((s.Purchases * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "%") : tickets(s.Purchases)) + "/" + TicketBudget(tickets) + "; meet target"; break; case "small_stakes": text2 = "Profit: " + Money(cash, s.SmallStakeProfit) + "/" + Money(cash, s.ContractTarget * 0.5) + "; bets " + Money(cash, 0.01) + " or less"; break; case "high_roller": text2 = Math.Min(3, s.HighRollerPlays) + "/3 bets of " + Money(cash, 0.03) + "+"; break; case "team_streak": text2 = "Best win streak: " + Math.Min(3, s.BestDayStreak) + "/3"; break; case "clean_run": if (s.RescueUsed) { return text + ": rebate or rescue used\nMissed today."; } text2 = "No rebates or rescues; meet target"; break; case "featured_game": text2 = ShortGame(game, s.ContractFeaturedGame) + ": " + Money(cash, FeaturedProfit(s)) + "/" + Money(cash, s.ContractTarget * 0.5) + " profit"; break; case "strong_finish": text2 = "Profit: " + Money(cash, s.NativeProfit) + "/" + Money(cash, 1.0 + s.ContractTarget * 0.5); break; case "balanced_crew": text2 = HudCrewProgress(s, wins: true); break; default: text2 = "Tab for goal details"; break; } string text3 = HudCash(s, ContractCash(s), cash); if (s.ContractId == "high_roller") { text3 = text3 + "; " + TicketReward(s, 1); } return text + ": " + text2 + "\n" + text3; } private static string HudRiskProgress(NegativeProgress task, Func cash) { string text = task.Id ?? ""; if (text.StartsWith("timed_", StringComparison.Ordinal)) { text = text.Substring(6); } int num = Math.Max(1, task.Required); int num2 = Math.Min(num, Math.Max(0, task.Count)); switch (text) { case "participation": if (task.Roster != null && task.Roster.Length > 1) { return "Everyone plays: " + num2 + "/" + num; } return "Finish games: " + num2 + "/" + num; case "variety": return "Different games: " + num2 + "/" + num; case "net_profit": return "Profit: " + Money(cash, task.Profit) + "/" + Money(cash, task.ProfitTarget); case "six_plays": return "Crew games: " + num2 + "/" + num; default: return "Task progress: " + num2 + "/" + num; } } private static string HudPenaltyOutcome(RunState s, NegativeProgress task, Func cash) { if (!string.IsNullOrEmpty(task.PenaltyId)) { if (s.PenaltyReceipts != null) { foreach (Reward penaltyReceipt in s.PenaltyReceipts) { if (penaltyReceipt.Id == task.PenaltyId) { return "missed, " + Money(cash, penaltyReceipt.Amount) + " deducted"; } } } if (s.PendingPenaltyRewards != null) { foreach (Reward pendingPenaltyReward in s.PendingPenaltyRewards) { if (pendingPenaltyReward.Id == task.PenaltyId) { return "missed, up to " + Money(cash, pendingPenaltyReward.Amount) + " deduction pending"; } } } return "missed, no confirmed deduction"; } return "missed, no loss budget left"; } private static string HudCash(RunState s, double amount, Func cash) { if (!(Remaining(s) <= 1E-12)) { return "up to " + Money(cash, Math.Min(Math.Max(0.0, amount), Remaining(s))); } return "bonus cap reached"; } private static string HudObjectiveCash(RunState s, bool complete, double amount, string description, Func cash) { if (!complete) { return HudCash(s, amount, cash); } double num = 0.0; double num2 = 0.0; bool flag = false; if (s.CashReceipts != null) { foreach (Reward cashReceipt in s.CashReceipts) { if (cashReceipt.Round == s.Round && cashReceipt.Day == s.Day && cashReceipt.Description == description) { num += cashReceipt.Amount; flag = true; } } } if (s.PendingCashRewards != null) { foreach (Reward pendingCashReward in s.PendingCashRewards) { if (pendingCashReward.Round == s.Round && pendingCashReward.Day == s.Day && pendingCashReward.Description == description) { num2 += pendingCashReward.Amount; flag = true; } } } if (!flag) { if (!s.LegacyCash) { return "done; no cash payment"; } return "done; older receipt unavailable"; } return ((num > 0.0) ? (Money(cash, num) + " paid") : "") + ((num2 > 0.0) ? (((num > 0.0) ? "; " : "") + Money(cash, num2) + " pending") : ""); } private static string HudCrewProgress(RunState s, bool wins) { int num = ((s.Players != null) ? s.Players.Length : 0); int num2 = 0; if (s.Players != null && s.PlayerProgress != null) { ulong[] players = s.Players; foreach (ulong num3 in players) { foreach (PlayerProgress item in s.PlayerProgress) { if (item.Id != num3) { continue; } if (num == 1) { return (wins ? "Wins: " : "Games: ") + Math.Min(wins ? 2 : 3, wins ? item.Wins : item.Plays) + "/" + (wins ? 2 : 3); } if (wins ? (item.Wins > 0) : (item.Plays > 0)) { num2++; } break; } } } return "Players: " + num2 + "/" + (wins ? Math.Min(2, num) : num) + (wins ? " won" : " played"); } private static string ShortGame(Func game, int id) { string text = Game(game, id); if (string.IsNullOrEmpty(text)) { return "Featured game"; } text = text.Replace('\n', ' ').Replace('\r', ' '); if (text.Length <= 24) { return text; } return text.Substring(0, 21) + "..."; } private static string Countdown(double seconds) { if (double.IsNaN(seconds) || double.IsInfinity(seconds) || seconds < 0.0) { seconds = 0.0; } int num = (int)Math.Min(86400.0, Math.Ceiling(seconds)); return (num / 60).ToString(CultureInfo.InvariantCulture) + ":" + (num % 60).ToString("00", CultureInfo.InvariantCulture); } private static string PenaltyOutcome(RunState s, NegativeProgress task, Func cash) { if (!string.IsNullOrEmpty(task.PenaltyId)) { if (s.PenaltyReceipts != null) { foreach (Reward penaltyReceipt in s.PenaltyReceipts) { if (penaltyReceipt.Id == task.PenaltyId) { return "Missed. Lost " + Money(cash, penaltyReceipt.Amount) + " shared cash."; } } } if (s.PendingPenaltyRewards != null) { foreach (Reward pendingPenaltyReward in s.PendingPenaltyRewards) { if (pendingPenaltyReward.Id == task.PenaltyId) { return "Missed. Loss pending: up to " + Money(cash, pendingPenaltyReward.Amount) + "."; } } } return "Missed. No confirmed cash loss."; } return "Missed. No cash lost; today's loss cap was reached."; } public static string Contract(RunState s, Func cash, Func game = null, Func tickets = null) { if (s == null || string.IsNullOrEmpty(s.ContractId)) { return "No bonus goal today."; } string text = ContractGoal(s, s.ContractId, cash, game, tickets); bool flag = s.ContractId == "everybody_in" && Active(s, "crew_relay"); if (s.ContractCompleted) { string text2 = CompletedCash(s, Name(Engine.Contracts, s.ContractId) + " complete", cash); string text3 = CompletedCash(s, "House Credit", cash, optional: true); return Name(Engine.Contracts, s.ContractId) + "\nComplete.\n" + text2 + ((text3.Length == 0) ? "" : ("\nHouse Credit: " + text3)); } string text4 = CashReward(s, ContractCash(s), cash); if (s.ContractId == "high_roller") { text4 = text4 + "; " + TicketReward(s, 1); } if (Perk(s, "house_credit") && s.HouseCreditChapter != s.Chapter) { double num = Math.Min(0.04, Math.Max(0.0, Remaining(s) - Math.Min(ContractCash(s), Remaining(s)))); text4 += ((num > 1E-12) ? ("\nHouse Credit: up to " + Money(cash, num) + " extra") : "\nHouse Credit: no bonus cash left"); } string text5 = ContractProgress(s, cash, tickets); if (flag) { text = "Same task as Crew Relay; both rewards count."; } if ((s.ContractId == "budget_night" && s.Purchases > 0.35) || (s.ContractId == "clean_run" && s.RescueUsed)) { return Name(Engine.Contracts, s.ContractId) + "\n" + text5 + "\nMissed today."; } return Name(Engine.Contracts, s.ContractId) + "\n" + text + "\n" + text5 + "\nReward: " + text4; } public static string ActionHint(RunState s, Func cash, Func game = null, Func tickets = null) { if (s == null || !s.DayOpen || !s.DayEnabled) { return "Bonuses start with the next casino day."; } return "Bet at least " + Money(cash, 0.001) + " (or the table minimum, if higher) and finish the game. Wins, losses and ties count.\nFree or refunded games don't count. A win pays more than you bet. Profit is winnings minus bets; Chaos bonuses don't count."; } public static string Perks(RunState s, Func cash, Func game = null, Func tickets = null) { if (s == null || s.PerkIds == null || s.PerkIds.Count == 0) { return "Survive " + ((s == null) ? 4 : ChapterLength(s)) + " casino days to choose your first perk."; } List list = new List(); foreach (string perkId in s.PerkIds) { list.Add(Name(Engine.Perks, perkId) + "\n" + PerkDescription(s, perkId, cash) + "\n" + PerkStatus(s, perkId)); } return string.Join("\n\n", list.ToArray()); } public static string[] Choices(RunState s, Func cash, Func game = null, Func tickets = null) { List list = new List(); if (s != null && s.Choices != null) { foreach (Choice choice in s.Choices) { if (choice != null) { string text = ((choice.Kind == "perk") ? PerkDescription(s, choice.Id, cash) : ((choice.Kind == "contract") ? (ContractGoal(s, choice.Id, cash, game, tickets) + " Reward: " + CashReward(s, ContractCash(s), cash) + ((choice.Id == "high_roller") ? ("; " + TicketReward(s, 1)) : "")) : ((choice.Id == "vault_cash") ? (CashReward(s, 0.2, cash) + ".") : ((!(choice.Id == "vault_tickets")) ? "Choose another crew perk." : ((TicketCount(s, 3) <= 0) ? "Today's ticket limit has been reached. No tickets available." : ("Up to " + TicketsLabel(TicketCount(s, 3), shared: true) + ".")))))); if (choice.Kind == "perk" && s.PerkIds != null && s.PerkIds.Count >= 3) { text = text + " Replaces " + Name(Engine.Perks, s.PerkIds[0]) + "."; } string text2 = ((choice.Kind == "perk") ? "Crew perk" : ((choice.Kind == "contract") ? "Bonus goal" : "Jackpot reward")); list.Add((string.IsNullOrEmpty(choice.Name) ? "Crew choice" : choice.Name) + "\n" + text2 + "\n" + text); } } } return list.ToArray(); } public static string Summary(RunState s, Func cash, Func game = null, Func tickets = null, double paidCashRatio = double.NaN) { if (s == null || s.Round < 1) { return "Start a casino day for your first modifiers and goals."; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Cash target: ").Append(Money(cash, s.QuotaFactor)); if (Math.Abs(s.QuotaFactor - 1.0) < 1E-09) { stringBuilder.Append(" (unchanged)"); } stringBuilder.Append("\nDaily bonus cap: ").Append(Money(cash, Cap(s))); stringBuilder.Append("\nBonus cash left: ").Append(Money(cash, Remaining(s))); if (s.LegacyCash) { stringBuilder.Append("\nOlder cash rewards have no detailed receipts."); } else if (!double.IsNaN(paidCashRatio) && !double.IsInfinity(paidCashRatio) && paidCashRatio >= 0.0) { stringBuilder.Append("\nBonuses paid: ").Append(Money(cash, paidCashRatio)); } int num = 0; if (s.PendingCashRewards != null) { foreach (Reward pendingCashReward in s.PendingCashRewards) { if (pendingCashReward.Round == s.Round && pendingCashReward.Day == s.Day) { num++; } } } if (num > 0) { stringBuilder.Append("\nPayments pending: ").Append(num); } stringBuilder.Append("\nThe cap only limits Chaos bonus cash. Casino winnings have no Chaos cap."); if (s.GoldenPayoutPending) { stringBuilder.Append("\nGolden Payout: the crew's next new winning bet gets matching profit, ").Append(CashReward(s, 0.2, cash)).Append(". Expires at day end."); } if (!string.IsNullOrEmpty(s.NextModifierPreview)) { stringBuilder.Append("\nNext modifier: ").Append(s.NextModifierPreview); } stringBuilder.Append("\nThis run: ").Append(s.DaysSurvived).Append(" days survived, ") .Append(s.ContractsCompleted) .Append(" goals completed, ") .Append(s.JackpotsFound) .Append(" jackpots."); return stringBuilder.ToString(); } public static string Jackpot(RunState s, string id, Func cash) { return id switch { "vault" => "Choose " + CashReward(s, 0.2, cash) + ", " + TicketReward(s, 3) + ", or a new perk.", "golden_payout" => "The crew's next new winning bet gets matching profit: " + CashReward(s, 0.2, cash) + ". Expires at day end.", "house_refund" => "Get half of today's casino losses back: " + CashReward(s, s.NativeLosses * 0.5, cash) + ".", "lucky_shipment" => "Choose another crew perk.", "debt_relief" => "Cash target drops by " + Money(cash, Math.Min(0.1, Math.Max(0.0, s.QuotaFactor - 0.65))) + "; never below " + Money(cash, 0.65) + ".", _ => "Jackpot available.", }; } public static string RunHistory(RunState s, Func cash) { if (s == null || s.Round < 1) { return "Start a casino day to begin this run."; } string text = "Days survived: " + s.DaysSurvived + "\nBonus goals completed: " + s.ContractsCompleted + "\nJackpots found: " + s.JackpotsFound; if (s.GoldenPayoutPending) { text = text + "\nGolden Payout: " + Jackpot(s, "golden_payout", cash); } if (!string.IsNullOrEmpty(s.NextModifierPreview)) { text = text + "\nNext modifier: " + s.NextModifierPreview; } return text; } private static string Modifier(RunState s, string id, Func cash, Func game, Func tickets) { return string.Concat(str2: id switch { "house_match" => "Earn 20% extra on casino profit.\nCash target: " + Money(cash, s.QuotaFactor) + "\nBonus left: " + CashReward(s, Remaining(s), cash), "closing_time" => "20% less time to hit a lower cash target.\nCash target: " + Money(cash, s.QuotaFactor), "pressure_cooker" => "Beat today's higher cash target: " + Money(cash, s.QuotaFactor) + ".\nReward at day end: " + TicketReward(s, 1), "breather" => "Reach today's lower cash target: " + Money(cash, s.QuotaFactor) + ".", "hot_table" => "Win at " + Game(game, s.FeaturedGame) + " for 25% extra profit.\nBonus left: " + CashReward(s, Remaining(s), cash), "variety_show" => "Win on " + AvailableCount(s) + " different game types.\nGame types won: " + (s.VarietyCompleted ? AvailableCount(s) : Math.Min(AvailableCount(s), GameCount(s, wins: true))) + "/" + AvailableCount(s) + "\n" + Complete(s.VarietyCompleted, s, 0.06, cash, "Variety Show complete"), "crew_relay" => CrewGoal(s, wins: false) + "\n" + CrewProgress(s, wins: false) + "\n" + Complete(s.RelayCompleted, s, 0.05, cash, "Crew Relay complete"), "risk_dividend" => "Finish 3 casino games with bets of " + Money(cash, 0.03) + " or more each.\nGames: " + (s.RiskDividendCompleted ? 3 : Math.Min(3, s.HighRollerPlays)) + "/3\n" + (s.RiskDividendCompleted ? "Complete. See today's ticket total." : ("Reward: " + TicketReward(s, 1))), "lucky_rotation" => "Win at " + Game(game, s.FeaturedGame) + " for 20% extra profit.\nFeatured game changes after 4 finished games.\nBonus left: " + CashReward(s, Remaining(s), cash), "rebate_night" => "Get 10% of casino losses back.\nBonus left: " + CashReward(s, Remaining(s), cash), "comeback_night" => Recovery(s, cash) + ".\n" + RecoveryProgress(s, cash) + "\n" + Complete(s.ComebackCompleted && HasOnce(s, "comeback"), s, 0.06, cash, "Comeback Night complete"), "streak_night" => "Win 3 crew games in a row.\nWin streak: " + (s.StreakCompleted ? 3 : Math.Min(3, s.WinningStreak)) + "/3\n" + Complete(s.StreakCompleted, s, 0.05, cash, "Streak Night complete"), "ticket_rush" => "Meet today's cash target: " + Money(cash, s.QuotaFactor) + ".\nReward at day end: " + TicketReward(s, 2), "frugal_friends" => "Meet today's target. Spend at most " + TicketBudget(tickets) + " in the shop.\n" + Spend(s, tickets) + "\n" + ((s.Purchases > 0.35) ? "Missed today; shop budget exceeded." : ("Reward at day end: " + CashReward(s, 0.05, cash))), "plain_luck" => "Perks paused today. Meet the cash target for a new perk choice.", "double_trouble" => "An extra modifier today. Meet the cash target for " + TicketReward(s, 1) + ".", "bet_it_all" => s.BetItAllUsed ? "Crew all-in used today. Choose a smaller bet.\nRefunds and same-day retries don't restore it." : "Once today, bet all shared cash on one casino game.\nOne use for the whole crew. Smaller bets save it. Refunds and same-day retries don't restore it. No extra cash bonus.", _ => "Modifier active.", }, str0: Name(Engine.Modifiers, id), str1: "\n"); } private static string ContractGoal(RunState s, string id, Func cash, Func game, Func tickets) { return id switch { "casino_tour" => "Play " + AvailableCount(s) + " different game types.", "everybody_in" => CrewGoal(s, wins: false), "profit_ladder" => "Make " + Money(cash, s.ContractTarget) + " casino profit after losses.", "recovery_crew" => Recovery(s, cash) + ".", "budget_night" => "Meet today's cash target. Spend at most " + TicketBudget(tickets) + " in the shop.", "small_stakes" => "Make " + Money(cash, s.ContractTarget * 0.5) + " profit after losses. Bet " + Money(cash, 0.01) + " or less each time.", "high_roller" => "Finish 3 casino games with bets of " + Money(cash, 0.03) + " or more each.", "team_streak" => "Win 3 crew games in a row.", "clean_run" => "Meet today's target without using Rebate Night, Safety Fund, House Refund or Debt Relief.", "featured_game" => "Make " + Money(cash, s.ContractTarget * 0.5) + " profit after losses at " + Game(game, s.ContractFeaturedGame) + ".", "strong_finish" => "Make " + Money(cash, 1.0 + s.ContractTarget * 0.5) + " casino profit after losses.", "balanced_crew" => CrewGoal(s, wins: true), _ => "Complete today's bonus goal.", }; } private static string ContractProgress(RunState s, Func cash, Func tickets) { switch (s.ContractId) { case "casino_tour": return "Game types: " + Math.Min(AvailableCount(s), GameCount(s, wins: false)) + "/" + AvailableCount(s); case "everybody_in": return CrewProgress(s, wins: false); case "profit_ladder": return "Profit: " + Money(cash, s.NativeProfit) + "/" + Money(cash, s.ContractTarget); case "recovery_crew": return RecoveryProgress(s, cash); case "budget_night": return Spend(s, tickets) + ((s.Purchases <= 0.35) ? "; finish the day" : ""); case "small_stakes": return "Small-bet profit: " + Money(cash, s.SmallStakeProfit) + "/" + Money(cash, s.ContractTarget * 0.5); case "high_roller": return "Games: " + Math.Min(3, s.HighRollerPlays) + "/3"; case "team_streak": return "Best win streak: " + Math.Min(3, s.BestDayStreak) + "/3"; case "clean_run": if (!s.RescueUsed) { return "No rebates or rescues used; finish the day"; } return "Rebate or rescue used."; case "featured_game": return "Profit: " + Money(cash, FeaturedProfit(s)) + "/" + Money(cash, s.ContractTarget * 0.5); case "strong_finish": return "Profit: " + Money(cash, s.NativeProfit) + "/" + Money(cash, 1.0 + s.ContractTarget * 0.5); case "balanced_crew": return CrewProgress(s, wins: true); default: return "Goal in progress"; } } private static string PerkDescription(RunState s, string id, Func cash) { return id switch { "scout" => "See the next day's modifier before that day starts.", "wildcard" => "Change today's modifiers before anyone bets. Once per " + ChapterLength(s) + "-day chapter; Closing Time stays locked.", "second_opinion" => "Change today's bonus goal before anyone bets. Once per " + ChapterLength(s) + "-day chapter.", "house_credit" => "Your first completed goal per " + ChapterLength(s) + "-day chapter adds " + CashReward(s, 0.04, cash) + ".", "safety_fund" => "Get 15% of the crew's first casino loss back each day. Maximum: " + CashReward(s, Remaining(s), cash) + ".", "team_dividend" => "Bonus goals pay 25% more. Today's reward: " + CashReward(s, BaseContractCash(s) * 1.25, cash) + ".", "second_draw" => "Get different offers when choosing a crew perk. Once per " + ChapterLength(s) + "-day chapter.", "negotiator" => "Choose between 2 bonus goals before betting. Available at each " + ChapterLength(s) + "-day chapter start.", _ => "Crew perk active.", }; } private static string PerkStatus(RunState s, string id) { if (Active(s, "plain_luck")) { return "Paused today by Plain Luck."; } if (!s.DayEnabled) { return "Active when Chaos Night is enabled."; } switch (id) { case "scout": if (!string.IsNullOrEmpty(s.NextModifierPreview)) { return "Next modifier: " + s.NextModifierPreview; } return "Next modifier: waiting for the next day."; case "wildcard": return RerollStatus(s, s.WildcardChapter == s.Chapter); case "second_opinion": if (s.SecondOpinionChapter != s.Chapter) { if (!string.IsNullOrEmpty(s.ContractId)) { return RerollStatus(s, used: false); } return "No bonus goal to change today."; } return "Used this chapter; available next chapter."; case "house_credit": if (s.HouseCreditChapter != s.Chapter) { return "Ready for your first completed goal this chapter."; } return "Used this chapter; available next chapter."; case "safety_fund": if (!s.SafetyFundUsed) { return "Ready for the crew's first casino loss today."; } return "Used today; available next casino day."; case "second_draw": if (s.SecondDrawChapter == s.Chapter) { return "Used this chapter; available next chapter."; } if (s.Choices != null) { foreach (Choice choice in s.Choices) { if (choice != null && choice.Kind == "perk") { return "Ready to change these perk offers."; } } } return "Available when a crew perk choice opens."; case "negotiator": return "Choose a bonus goal at each chapter start."; default: return "Active for the whole crew."; } } private static string RerollStatus(RunState s, bool used) { if (used) { return "Used this chapter; available next chapter."; } if (!s.DayOpen) { return "Available before betting on the next casino day."; } if (s.QualifyingPlays <= 0) { return "Ready before anyone bets today."; } return "Betting has started; wait for the next casino day."; } private static string Complete(bool completed, RunState s, double amount, Func cash, string description) { if (!completed) { return "Reward: " + CashReward(s, amount, cash); } return "Complete. " + CompletedCash(s, description, cash); } private static string CompletedCash(RunState s, string description, Func cash, bool optional = false) { double num = 0.0; double num2 = 0.0; bool flag = false; if (s.CashReceipts != null) { foreach (Reward cashReceipt in s.CashReceipts) { if (cashReceipt.Round == s.Round && cashReceipt.Day == s.Day && cashReceipt.Description == description) { num += cashReceipt.Amount; flag = true; } } } if (s.PendingCashRewards != null) { foreach (Reward pendingCashReward in s.PendingCashRewards) { if (pendingCashReward.Round == s.Round && pendingCashReward.Day == s.Day && pendingCashReward.Description == description) { num2 += pendingCashReward.Amount; flag = true; } } } if (optional && !flag) { return ""; } if (!flag) { if (s.LegacyCash) { return "Older payment; detailed receipt unavailable."; } if (s.CashReceipts != null && s.CashReceipts.Count >= 64) { return "No payment pending. Older receipts may be unavailable."; } return "No cash payment; daily bonus cap or payment queue was full."; } string text = ((num > 0.0) ? ("Paid: " + Money(cash, num) + " shared cash.") : ""); if (num2 > 0.0) { string text2 = text; text = text2 + ((text.Length > 0) ? " " : "") + "Payment pending: " + Money(cash, num2) + " shared cash."; } return text; } private static string CashReward(RunState s, double amount, Func cash) { if (!(Remaining(s) <= 1E-12)) { return "up to " + Money(cash, Math.Min(Math.Max(0.0, amount), Remaining(s))) + " shared cash"; } return "daily bonus cap reached"; } private static double Cap(RunState s) { if (s.ActiveRules != null) { return s.ActiveRules.MaxBonusQuotaFraction; } return 0.35; } private static double Remaining(RunState s) { return Math.Max(0.0, Cap(s) - s.CashAwarded); } private static double BaseContractCash(RunState s) { return 0.06 + 0.01 * (double)Math.Min(4, Math.Max(0, s.Chapter - 1)); } private static double ContractCash(RunState s) { return BaseContractCash(s) * (Perk(s, "team_dividend") ? 1.25 : 1.0); } private static bool Active(RunState s, string id) { if (s.ModifierIds != null) { return s.ModifierIds.Contains(id); } return false; } private static bool Perk(RunState s, string id) { if (s.PerkIds != null && s.PerkIds.Contains(id)) { return !Active(s, "plain_luck"); } return false; } private static int ChapterLength(RunState s) { if (s.ActiveRules != null) { return s.ActiveRules.ChapterRounds; } return 4; } private static int TicketCount(RunState s, int amount) { return Math.Min(amount, Math.Max(0, ((s.ActiveRules == null) ? 3 : s.ActiveRules.MaxTicketsPerDay) - s.TicketsAwarded)); } private static string TicketBudget(Func tickets) { if (tickets != null) { return tickets(0.35); } return "35% of today's starting tickets"; } private static string Spend(RunState s, Func tickets) { return "Shop: " + ((tickets == null) ? ((s.Purchases * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "% of starting tickets") : tickets(s.Purchases)) + "/" + TicketBudget(tickets) + ((s.Purchases > 0.35) ? " (over budget)" : ""); } private static string TicketReward(RunState s, int amount) { int num = TicketCount(s, amount); if (num > 0) { return "up to " + TicketsLabel(num, shared: false); } return "daily ticket cap reached"; } private static string TicketsLabel(int amount, bool shared) { return amount + (shared ? " shared " : " ") + ((amount == 1) ? "ticket" : "tickets"); } private static string Money(Func cash, double ratio) { if (cash != null) { return cash(ratio); } return (ratio * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "% of the original cash target"; } private static string Game(Func game, int id) { if (game != null) { return game(id); } return "the featured casino game"; } private static string Name(Definition[] definitions, string id) { foreach (Definition definition in definitions) { if (definition.Id == id) { return definition.Name; } } return "Bonus"; } private static int AvailableCount(RunState s) { return Math.Min(3, (s.AvailableGames != null) ? s.AvailableGames.Length : 0); } private static int GameCount(RunState s, bool wins) { int num = 0; if (s.GameProgress != null) { foreach (GameProgress item in s.GameProgress) { if (wins ? (item.Wins > 0) : (item.Plays > 0)) { num++; } } } return num; } private static double FeaturedProfit(RunState s) { if (s.GameProgress != null) { foreach (GameProgress item in s.GameProgress) { if (item.Id == s.ContractFeaturedGame) { return item.Profit; } } } return 0.0; } private static bool HasOnce(RunState s, string key) { if (s.RewardIds != null) { return s.RewardIds.Contains(s.RunId + "/d" + s.Round + "/once/" + key); } return false; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } private static string CrewGoal(RunState s, bool wins) { int num = ((s.Players != null) ? s.Players.Length : 0); if (num <= 1) { if (!wins) { return "Finish 3 casino games."; } return "Win 2 casino games."; } if (!wins) { return "All " + num + " starting players finish 1 casino game each."; } return Math.Min(2, num) + " different players win 1 casino game each."; } private static string CrewProgress(RunState s, bool wins) { int num = ((s.Players != null) ? s.Players.Length : 0); int num2 = 0; if (s.Players != null && s.PlayerProgress != null) { ulong[] players = s.Players; foreach (ulong num3 in players) { foreach (PlayerProgress item in s.PlayerProgress) { if (item.Id != num3) { continue; } if (num == 1) { return (wins ? "Wins: " : "Games: ") + Math.Min(wins ? 2 : 3, wins ? item.Wins : item.Plays) + "/" + (wins ? 2 : 3); } if (wins ? (item.Wins > 0) : (item.Plays > 0)) { num2++; } break; } } } return "Players: " + num2 + "/" + (wins ? Math.Min(2, num) : num) + (wins ? " won" : " played"); } private static string Recovery(RunState s, Func cash) { return "After shared cash drops by " + Money(cash, 0.1) + ", win back " + Money(cash, 0.1) + " from its lowest point"; } private static string RecoveryProgress(RunState s, Func cash) { if (!s.ComebackCompleted) { if (!s.HadSetback) { return "Waiting for a " + Money(cash, 0.1) + " cash drop"; } return "Win back " + Money(cash, Math.Max(0.0, s.LowBalance + 0.1 - s.LastBalance)) + " more"; } return "Recovery complete"; } } } namespace ChaosNight { internal static class ChaosProgressPresentation { internal static UiProgress Measure(double current, double target, string caption, bool complete = false, bool failed = false) { if (!Finite(current) || !Finite(target) || target <= 0.0) { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = caption ?? ""; uiProgress.Complete = complete; uiProgress.Failed = failed; return uiProgress; } UiProgress uiProgress2 = new UiProgress(); uiProgress2.Available = true; uiProgress2.Fraction = (complete ? 1.0 : Math.Max(0.0, Math.Min(1.0, current / target))); uiProgress2.Caption = caption ?? ""; uiProgress2.Complete = complete; uiProgress2.Failed = failed; return uiProgress2; } internal static UiProgress[] Modifiers(RunState state, double balance, Func cash) { if (state == null || state.ModifierIds == null) { return new UiProgress[0]; } List list = new List(); foreach (string modifierId in state.ModifierIds) { UiProgress uiProgress2; switch (modifierId) { case "variety_show": uiProgress2 = Count(GameCount(state, wins: true), GameTarget(state), "game types won", state.VarietyCompleted); break; case "crew_relay": uiProgress2 = Crew(state, wins: false, state.RelayCompleted); break; case "risk_dividend": uiProgress2 = Count(state.HighRollerPlays, 3, "large bets finished", state.RiskDividendCompleted); break; case "streak_night": uiProgress2 = Count(state.WinningStreak, 3, "wins in a row", state.StreakCompleted); break; case "comeback_night": uiProgress2 = Recovery(state, cash, state.ComebackCompleted); break; case "house_match": case "hot_table": case "rebate_night": uiProgress2 = BonusCap(state, cash); break; case "lucky_rotation": { UiProgress uiProgress5; if (state.AvailableGames == null || state.AvailableGames.Length <= 1) { UiProgress uiProgress4 = new UiProgress(); uiProgress4.Caption = ((state.AvailableGames != null && state.AvailableGames.Length == 1) ? "The only available game stays featured" : "Waiting for available casino games"); uiProgress5 = uiProgress4; } else { uiProgress5 = Count(Math.Max(0, state.RotationPlays) % 4, 4, "games finished this rotation"); } uiProgress2 = uiProgress5; break; } case "closing_time": case "pressure_cooker": case "breather": case "ticket_rush": case "frugal_friends": case "plain_luck": case "double_trouble": uiProgress2 = Target(state, balance, cash); if (modifierId == "frugal_friends" && state.Purchases > 0.35) { uiProgress2.Failed = true; } break; case "bet_it_all": { UiProgress uiProgress3 = new UiProgress(); uiProgress3.Caption = (state.BetItAllUsed ? "Crew all-in used today" : "1 shared all-in available today"); uiProgress2 = uiProgress3; break; } default: { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Active today"; uiProgress2 = uiProgress; break; } } list.Add(uiProgress2); } return list.ToArray(); } internal static UiProgress[] Risks(RunState state, Func cash) { if (state == null || state.Negatives == null) { return new UiProgress[0]; } List list = new List(); foreach (NegativeProgress negative in state.Negatives) { if (negative == null) { continue; } string text = negative.Id ?? ""; string text2 = (text.StartsWith("timed_", StringComparison.Ordinal) ? text.Substring(6) : text); UiProgress uiProgress2; switch (text2) { case "net_profit": uiProgress2 = Money(negative.Profit, negative.ProfitTarget, cash, negative.Completed, negative.Failed); break; case "participation": case "variety": case "six_plays": { UiProgress uiProgress4; if (negative.Required <= 0) { UiProgress uiProgress3 = new UiProgress(); uiProgress3.Caption = "Progress unavailable"; uiProgress3.Complete = negative.Completed; uiProgress3.Failed = negative.Failed; uiProgress4 = uiProgress3; } else { uiProgress4 = Count(negative.Count, negative.Required, (text2 == "participation" && negative.Roster != null && negative.Roster.Length > 1) ? "players finished a game" : ((text2 == "variety") ? "game types played" : "games finished"), negative.Completed, negative.Failed); } uiProgress2 = uiProgress4; break; } default: { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Progress unavailable"; uiProgress.Complete = negative.Completed; uiProgress.Failed = negative.Failed; uiProgress2 = uiProgress; break; } } if (negative.Completed && negative.WaivedCompletion) { uiProgress2.Caption = "Completed after a player left"; } else if (!negative.Completed && !negative.Failed) { UiProgress uiProgress5 = uiProgress2; uiProgress5.Caption = uiProgress5.Caption + " · " + Deadline(negative); } list.Add(uiProgress2); } return list.ToArray(); } internal static UiProgress Goal(RunState state, double balance, Func cash) { if (state == null || string.IsNullOrEmpty(state.ContractId)) { return new UiProgress(); } bool contractCompleted = state.ContractCompleted; switch (state.ContractId) { case "casino_tour": return Count(GameCount(state, wins: false), GameTarget(state), "game types played", contractCompleted); case "everybody_in": return Crew(state, wins: false, contractCompleted); case "profit_ladder": return Money(state.NativeProfit, state.ContractTarget, cash, contractCompleted); case "recovery_crew": return Recovery(state, cash, contractCompleted); case "small_stakes": return Money(state.SmallStakeProfit, state.ContractTarget * 0.5, cash, contractCompleted); case "high_roller": return Count(state.HighRollerPlays, 3, "large bets finished", contractCompleted); case "team_streak": return Count(state.BestDayStreak, 3, "best win streak", contractCompleted); case "featured_game": return Money(FeaturedProfit(state), state.ContractTarget * 0.5, cash, contractCompleted); case "strong_finish": return Money(state.NativeProfit, 1.0 + state.ContractTarget * 0.5, cash, contractCompleted); case "balanced_crew": return Crew(state, wins: true, contractCompleted); case "budget_night": case "clean_run": { UiProgress uiProgress = Target(state, balance, cash); uiProgress.Complete = contractCompleted; uiProgress.Failed = ((state.ContractId == "budget_night") ? (state.Purchases > 0.35) : state.RescueUsed); if (contractCompleted) { uiProgress.Fraction = 1.0; uiProgress.Caption = "Cash target reached"; } else if (uiProgress.Available && uiProgress.Fraction >= 1.0 && !uiProgress.Failed) { uiProgress.Caption = "Cash target reached; finish the day"; } return uiProgress; } default: return new UiProgress(); } } internal static UiProgress Target(RunState state, double balance, Func cash) { if (state == null) { return new UiProgress(); } if (!Finite(balance) || !Finite(state.QuotaFactor) || state.QuotaFactor <= 0.0) { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Waiting for the shared cash balance"; return uiProgress; } return Measure(balance, state.QuotaFactor, Amount(cash, balance) + " / " + Amount(cash, state.QuotaFactor) + " shared cash"); } internal static UiProgress BonusCap(RunState state, Func cash) { if (state == null) { return new UiProgress(); } double num = ((state.ActiveRules == null) ? 0.35 : state.ActiveRules.MaxBonusQuotaFraction); if (!Finite(num) || num < 0.0 || !Finite(state.CashAwarded)) { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Bonus cash total unavailable"; return uiProgress; } if (num == 0.0) { UiProgress uiProgress2 = new UiProgress(); uiProgress2.Caption = "Bonus cash is turned off today"; return uiProgress2; } return Measure(state.CashAwarded, num, Amount(cash, state.CashAwarded) + " / " + Amount(cash, num) + " bonus cash used (includes pending payments)"); } private static UiProgress Money(double value, double target, Func cash, bool done = false, bool failed = false) { if (done && Finite(target) && target > 0.0) { return Measure(target, target, Amount(cash, target) + " profit target reached", complete: true, failed); } return Measure(value, target, (!Finite(value) || !Finite(target) || target <= 0.0) ? "Profit progress unavailable" : (Amount(cash, value) + " / " + Amount(cash, target) + " casino profit"), done, failed); } private static UiProgress Count(int value, int target, string unit, bool done = false, bool failed = false) { if (target <= 0) { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Progress unavailable"; uiProgress.Complete = done; uiProgress.Failed = failed; return uiProgress; } int num = (done ? target : Math.Max(0, Math.Min(target, value))); return Measure(value, target, num.ToString(CultureInfo.InvariantCulture) + " / " + target.ToString(CultureInfo.InvariantCulture) + " " + unit, done, failed); } private static UiProgress Recovery(RunState state, Func cash, bool done) { if (!done && (!state.HadSetback || !state.BalanceKnown)) { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Starts after shared cash drops by " + Amount(cash, 0.1); return uiProgress; } if (done) { return Measure(0.1, 0.1, Amount(cash, 0.1) + " cash recovery reached", complete: true); } double num = state.LastBalance - state.LowBalance; return Measure(num, 0.1, (!Finite(num)) ? "Cash recovery unavailable" : (Amount(cash, Math.Max(0.0, num)) + " / " + Amount(cash, 0.1) + " cash won back")); } private static UiProgress Crew(RunState state, bool wins, bool done) { int num = ((state.Players != null) ? state.Players.Length : 0); if (num == 0 || state.PlayerProgress == null) { UiProgress uiProgress = new UiProgress(); uiProgress.Caption = "Waiting for crew progress"; uiProgress.Complete = done; return uiProgress; } int num2 = 0; ulong[] players = state.Players; foreach (ulong num3 in players) { bool flag = false; foreach (PlayerProgress item in state.PlayerProgress) { if (item == null || item.Id != num3) { continue; } flag = true; if (num == 1) { return Count(wins ? item.Wins : item.Plays, wins ? 2 : 3, wins ? "solo wins" : "solo games", done); } if (wins ? (item.Wins > 0) : (item.Plays > 0)) { num2++; } break; } if (!flag && !done) { UiProgress uiProgress2 = new UiProgress(); uiProgress2.Caption = "Waiting for crew progress"; return uiProgress2; } } return Count(num2, wins ? Math.Min(2, num) : num, wins ? "players won a game" : "players finished a game", done); } private static int GameTarget(RunState state) { return Math.Min(3, (state.AvailableGames != null) ? state.AvailableGames.Length : 0); } private static int GameCount(RunState state, bool wins) { int num = 0; if (state.GameProgress != null) { foreach (GameProgress item in state.GameProgress) { if (item != null && (wins ? (item.Wins > 0) : (item.Plays > 0))) { num++; } } } return num; } private static double FeaturedProfit(RunState state) { if (state.GameProgress != null) { foreach (GameProgress item in state.GameProgress) { if (item != null && item.Id == state.ContractFeaturedGame) { return item.Profit; } } } return 0.0; } private static string Amount(Func cash, double value) { if (cash != null) { return cash(value); } return (value * 100.0).ToString("0.#", CultureInfo.InvariantCulture) + "%"; } private static string Deadline(NegativeProgress task) { if (!task.Timed) { return "by day end"; } if (!Finite(task.DeadlineSeconds) || !Finite(task.RemainingSeconds) || task.RemainingSeconds < 0.0) { return "time remaining unavailable"; } if (task.DeadlineSeconds <= 0.0) { return "timer starts with the casino clock"; } int num = (int)Math.Min(86400.0, Math.Ceiling(task.RemainingSeconds)); return (num / 60).ToString(CultureInfo.InvariantCulture) + ":" + (num % 60).ToString("00", CultureInfo.InvariantCulture) + " left"; } private static bool Finite(double value) { if (!double.IsNaN(value)) { return !double.IsInfinity(value); } return false; } } internal static class ChaosResetTicketBaseline { internal static bool MatchesPendingRestore(SaveData current, SaveData pending) { if (current != null && pending != null && current.saveName == pending.saveName && current.seed == pending.seed) { return current.daysPassed == pending.daysPassed; } return false; } internal static long ForDay(GameSettings settings, SaveGameRules rules, int day) { if ((Object)(object)settings == (Object)null || rules == null || settings.floorData == null || settings.floorData.Count == 0) { throw new InvalidOperationException("The native ticket schedule is unavailable."); } if (day < 1 || day > 9999) { throw new ArgumentOutOfRangeException("day"); } if (rules.startingTickets < 0 || settings.dailyTicketReward < 0) { throw new InvalidOperationException("The native ticket schedule contains a negative grant."); } long startingTickets = rules.startingTickets; checked { startingTickets += unchecked((long)settings.dailyTicketReward) * unchecked((long)day); for (int i = 0; i < day - 1; i++) { int ticketReward = settings.GetTicketReward(i); if (ticketReward < 0) { throw new InvalidOperationException("The native ticket schedule contains a negative grant."); } startingTickets += ticketReward; } return startingTickets; } } } public static class RoundTravelPolicy { public const int MaximumParty = 32; public static bool CanReady(bool enabled, bool server, bool roundSummary, bool skippable, bool completedHostHold, bool nativeHostReady, bool sceneReady, bool wagersIdle) { if (enabled && server && roundSummary && skippable && completedHostHold && nativeHostReady && sceneReady) { return wagersIdle; } return false; } public static bool CanCallPhone(bool enabled, bool server, bool home, bool initialized, bool partyReady, bool alreadyCalled, bool doorsOpen) { if (enabled && server && home && initialized && partyReady && !alreadyCalled) { return !doorsOpen; } return false; } public static bool SameParty(IList connections, IList nativePlayers, int host) { if (connections == null || nativePlayers == null || connections.Count == 0 || connections.Count > 32 || connections.Count != nativePlayers.Count) { return false; } HashSet hashSet = new HashSet(); foreach (int connection in connections) { if (connection < 0 || !hashSet.Add(connection)) { return false; } } if (!hashSet.Contains(host)) { return false; } HashSet hashSet2 = new HashSet(); foreach (int nativePlayer in nativePlayers) { if (!hashSet2.Add(nativePlayer) || !hashSet.Contains(nativePlayer)) { return false; } } return hashSet2.Count == hashSet.Count; } public static bool OwnsDelay(float current, float applied) { if (!float.IsNaN(current) && !float.IsInfinity(current)) { return Math.Abs(current - applied) <= 1E-05f; } return false; } public static bool CanRecoverCountdown(bool enabled, bool server, bool sameHost, bool sameScene, bool ownedZone, bool ownsHandle, bool appropriatePhase, bool transitioning, bool loadingScene) { if (enabled && server && sameHost && sameScene && ownedZone && ownsHandle && appropriatePhase && !transitioning) { return !loadingScene; } return false; } public static bool CanRecoverProgression(bool ownedTarget, bool initiallyReady, bool home, bool stillInteractable) { if (ownedTarget && initiallyReady) { if (!home) { return stillInteractable; } return true; } return false; } public static string IdleCountdownText(float delay) { if (!float.IsNaN(delay) && !float.IsInfinity(delay) && !(delay <= 0f)) { return delay.ToString("0.0", CultureInfo.CurrentCulture); } return null; } } internal sealed class RoundTravelCountdownGuard : IEnumerator, IDisposable { private readonly IEnumerator native; private readonly Func ready; private readonly Action cancel; private readonly Action failed; private bool started; private bool stopped; private bool pendingStop; private bool disposed; private Exception pendingError; private object current; public object Current => current; public RoundTravelCountdownGuard(IEnumerator source, Func canDepart, Action cancellation, Action failure) { native = source; ready = canDepart; cancel = cancellation; failed = failure; } public void Reset() { throw new NotSupportedException(); } public bool MoveNext() { if (stopped || disposed) { return false; } if (pendingStop) { Stop(pendingError); return false; } bool flag = !started; started = true; try { if (!ready()) { if (flag) { pendingStop = true; current = null; return true; } Stop(null); return false; } if (!native.MoveNext()) { stopped = true; Dispose(); return false; } current = native.Current; return true; } catch (Exception error) { if (flag) { pendingStop = true; pendingError = error; current = null; return true; } Stop(error); return false; } } private void Stop(Exception error) { stopped = true; try { if (error == null) { cancel(); } else { failed(error); } } finally { Dispose(); } } public void Dispose() { if (disposed) { return; } disposed = true; if (!(native is IDisposable disposable)) { return; } try { disposable.Dispose(); } catch (Exception obj) { failed(obj); } } } public sealed class ChaosRoundTravel : IDisposable { private sealed class DoorState { public VehicleDoors Doors; public AllPlayersTriggerZone Zone; public float PreviousDelay; public bool Owned; public bool ChangedExternally; public TMP_Text CountdownLabel; public string PreviousCountdownText; public string AppliedCountdownText; } private sealed class CountdownAttempt { public NetworkConnectionToClient Host; public int Scene; public Coroutine Handle; public SceneSwitcher Switcher; public bool InitiallyInteractable; } internal static ChaosRoundTravel Active; private readonly Func canUse; private readonly Func> readyParty; private readonly Func wagersIdle; private readonly Action log; private readonly List phones = new List(); private readonly List doors = new List(); private readonly HashSet reportedErrors = new HashSet(); private bool disposed; private bool fillingParty; private float nextTick; private SkipUI heldSummary; private NetworkIdentity heldHost; private int heldScene; private int heldDay; private static readonly FieldInfo ManagerInstance = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly FieldInfo LocalInstance = AccessTools.Field(typeof(LocalManager), "_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 ManagerSummary = AccessTools.Field(typeof(GameManager), "daySummaryUI"); private static readonly FieldInfo SummarySkip = AccessTools.Field(typeof(DaySummaryUI), "skipUI"); private static readonly FieldInfo Skippable = AccessTools.Field(typeof(SkipUI), "_isSkippable"); private static readonly FieldInfo SkippableLocal = AccessTools.Field(typeof(SkipUI), "_isSkippableLocal"); private static readonly FieldInfo HasSkipped = AccessTools.Field(typeof(SkipUI), "_hasSkipped"); private static readonly FieldInfo SkippedPlayers = AccessTools.Field(typeof(SkipUI), "_skippedPlayers"); private static readonly FieldInfo PhoneDoors = AccessTools.Field(typeof(PhoneBooth), "vehicleDoors"); private static readonly FieldInfo PhoneCalled = AccessTools.Field(typeof(PhoneBooth), "_isVehicleCalled"); private static readonly FieldInfo DoorsOpen = AccessTools.Field(typeof(VehicleDoors), "_doorsOpen"); private static readonly FieldInfo DoorTrigger = AccessTools.Field(typeof(VehicleDoors), "triggerZone"); private static readonly FieldInfo DepartureDelay = AccessTools.Field(typeof(AllPlayersTriggerZone), "delayBeforeEvent"); private static readonly FieldInfo Countdown = AccessTools.Field(typeof(AllPlayersTriggerZone), "_countdownRoutine"); private static readonly FieldInfo Triggered = AccessTools.Field(typeof(AllPlayersTriggerZone), "_hasTriggered"); private static readonly FieldInfo DepartureEvent = AccessTools.Field(typeof(AllPlayersTriggerZone), "onCountDownEnd"); private static readonly FieldInfo SwitcherInteractable = AccessTools.Field(typeof(SceneSwitcher), "isInteractable"); private static readonly FieldInfo CountdownLabel = AccessTools.Field(typeof(AllPlayersTriggerZone), "countdownText"); private static readonly FieldInfo CountdownTextRoutine = AccessTools.Field(typeof(AllPlayersTriggerZone), "_countdownTextRoutine"); private static readonly MethodInfo CallPhone = AccessTools.Method(typeof(PhoneBooth), "ServerFirstInteraction", (Type[])null, (Type[])null); private static readonly MethodInfo CountdownText = AccessTools.Method(typeof(AllPlayersTriggerZone), "RpcUpdateCountdownText", (Type[])null, (Type[])null); public ChaosRoundTravel(Func canUseNativeFeatures, Func> readyNativeParty, Func allWagersIdle, Action logger) { if (canUseNativeFeatures == null || readyNativeParty == null || allWagersIdle == null) { throw new ArgumentNullException("Native travel guards"); } if (Active != null) { throw new InvalidOperationException("Native travel already initialized."); } canUse = canUseNativeFeatures; readyParty = readyNativeParty; wagersIdle = allWagersIdle; log = logger; if (!NativeApiAvailable()) { throw new InvalidOperationException("Verified native travel API is unavailable."); } Active = this; } public void Tick() { //IL_0104: 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_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0192: Unknown result type (might be due to invalid IL or missing references) //IL_0197: Unknown result type (might be due to invalid IL or missing references) //IL_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a0: Unknown result type (might be due to invalid IL or missing references) //IL_01a5: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) if (disposed || Time.unscaledTime < nextTick) { return; } nextTick = Time.unscaledTime + 0.5f; try { bool flag = FeaturesEnabled(); for (int num = doors.Count - 1; num >= 0; num--) { DoorState doorState = doors[num]; if ((Object)(object)doorState.Doors == (Object)null || (Object)(object)doorState.Zone == (Object)null) { doors.RemoveAt(num); } else { ApplyDelay(doorState, flag); } } for (int num2 = phones.Count - 1; num2 >= 0; num2--) { if ((Object)(object)phones[num2] == (Object)null) { phones.RemoveAt(num2); } } if (!NetworkServer.active) { ClearHold(); return; } GameManager val = CurrentManager(); if ((Object)(object)val == (Object)null) { ClearHold(); return; } if ((Object)(object)heldSummary != (Object)null) { TryReadyParty(val); } if (!flag) { return; } Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != "HomeScene" || (int)val.state != 0 || !TryParty(out var party) || !NativeSceneReady(val, party)) { return; } foreach (PhoneBooth phone in phones) { if ((Object)(object)phone == (Object)null || !((NetworkBehaviour)phone).isServer || !((Component)phone).gameObject.activeInHierarchy) { continue; } Scene scene = ((Component)phone).gameObject.scene; SceneHandle handle = ((Scene)(ref scene)).handle; Scene activeScene2 = SceneManager.GetActiveScene(); if (!(handle != ((Scene)(ref activeScene2)).handle)) { object? value = PhoneDoors.GetValue(phone); VehicleDoors val2 = (VehicleDoors)((value is VehicleDoors) ? value : null); if (!((Object)(object)val2 == (Object)null) && ((NetworkBehaviour)val2).isServer && RoundTravelPolicy.CanCallPhone(flag, server: true, home: true, initialized: true, partyReady: true, (bool)PhoneCalled.GetValue(phone), (bool)DoorsOpen.GetValue(val2))) { RegisterDoor(val2); CallPhone.Invoke(phone, null); Write("Automatically called the native Home limo."); } } } } catch (Exception error) { Report("travel lifecycle", error); } } internal void RegisterPhone(PhoneBooth phone) { if (!disposed && !((Object)(object)phone == (Object)null) && !phones.Contains(phone) && phones.Count < 8) { phones.Add(phone); object? value = PhoneDoors.GetValue(phone); VehicleDoors val = (VehicleDoors)((value is VehicleDoors) ? value : null); if ((Object)(object)val != (Object)null) { RegisterDoor(val); } } } internal void RegisterDoor(VehicleDoors vehicle) { if (disposed || (Object)(object)vehicle == (Object)null) { return; } object? value = DoorTrigger.GetValue(vehicle); AllPlayersTriggerZone val = (AllPlayersTriggerZone)((value is AllPlayersTriggerZone) ? value : null); if ((Object)(object)val == (Object)null) { return; } foreach (DoorState door in doors) { if ((Object)(object)door.Doors == (Object)(object)vehicle) { ApplyDelay(door, FeaturesEnabled()); return; } } if (doors.Count < 8) { DoorState doorState = new DoorState(); doorState.Doors = vehicle; doorState.Zone = val; doorState.PreviousDelay = GetDelay(val); ref TMP_Text countdownLabel = ref doorState.CountdownLabel; object? value2 = CountdownLabel.GetValue(val); countdownLabel = (TMP_Text)((value2 is TMP_Text) ? value2 : null); DoorState doorState2 = doorState; doors.Add(doorState2); ApplyDelay(doorState2, FeaturesEnabled()); } } internal void SceneInitialized() { //IL_0009: 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) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0062: 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_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00a8: 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_00b3: Unknown result type (might be due to invalid IL or missing references) if (disposed) { return; } try { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name != "HomeScene" && ((Scene)(ref activeScene)).name != "CasinoScene") { ClearHold(); return; } PhoneBooth[] array = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (PhoneBooth val in array) { Scene scene = ((Component)val).gameObject.scene; if (((Scene)(ref scene)).handle == ((Scene)(ref activeScene)).handle) { RegisterPhone(val); } } VehicleDoors[] array2 = Object.FindObjectsByType((FindObjectsSortMode)0); foreach (VehicleDoors val2 in array2) { Scene scene2 = ((Component)val2).gameObject.scene; if (((Scene)(ref scene2)).handle == ((Scene)(ref activeScene)).handle) { RegisterDoor(val2); } } } catch (Exception error) { Report("travel scene registration", error); } } internal void HostHoldCompleted(SkipUI skip) { //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_0094: Unknown result type (might be due to invalid IL or missing references) if (disposed || !NetworkServer.active || !FeaturesEnabled()) { return; } try { GameManager val = CurrentManager(); if (IsDaySummary(val, skip) && (bool)Skippable.GetValue(skip) && (bool)SkippableLocal.GetValue(skip)) { NetworkConnectionToClient localConnection = (NetworkConnectionToClient)(object)NetworkServer.localConnection; if (localConnection != null && !((Object)(object)((NetworkConnection)localConnection).identity == (Object)null) && object.ReferenceEquals(NetworkClient.localPlayer, ((NetworkConnection)localConnection).identity)) { heldSummary = skip; heldHost = ((NetworkConnection)localConnection).identity; Scene activeScene = SceneManager.GetActiveScene(); heldScene = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle); heldDay = val.daysPassed; } } } catch (Exception error) { Report("host hold", error); } } internal void NativeSkipAccepted(SkipUI skip, NetworkConnectionToClient sender) { if (disposed || fillingParty || (Object)(object)skip != (Object)(object)heldSummary || sender == null || !object.ReferenceEquals(sender, NetworkServer.localConnection)) { return; } try { TryReadyParty(CurrentManager()); } catch (Exception error) { Report("party readiness", error); } } private void TryReadyParty(GameManager manager) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003e: 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_004d: Unknown result type (might be due to invalid IL or missing references) if (fillingParty || (Object)(object)heldSummary == (Object)null) { return; } NetworkConnectionToClient localConnection = (NetworkConnectionToClient)(object)NetworkServer.localConnection; if (IsDaySummary(manager, heldSummary) && manager.daysPassed == heldDay) { Scene activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).handle != SceneHandle.op_Implicit(heldScene)) && localConnection != null && object.ReferenceEquals(((NetworkConnection)localConnection).identity, heldHost) && (bool)Skippable.GetValue(heldSummary)) { List party; bool flag = TryParty(out party); HashSet hashSet = SkippedPlayers.GetValue(heldSummary) as HashSet; bool nativeHostReady = hashSet != null && hashSet.Contains(localConnection) && (bool)HasSkipped.GetValue(heldSummary); if (!RoundTravelPolicy.CanReady(FeaturesEnabled(), NetworkServer.active, roundSummary: true, skippable: true, completedHostHold: true, nativeHostReady, flag && NativeSceneReady(manager, party), flag && WagersIdle())) { return; } foreach (NetworkConnectionToClient item in hashSet) { if (!party.Contains(item)) { return; } } fillingParty = true; SkipUI val = heldSummary; try { foreach (NetworkConnectionToClient item2 in party) { if ((bool)Skippable.GetValue(val)) { val.ServerRegisterSkipFromConnection(item2); continue; } break; } Write("Host readied the native round-summary party (" + party.Count + " players)."); ClearHold(); return; } finally { fillingParty = false; } } } ClearHold(); } internal void ResetSummary(SkipUI skip) { if ((Object)(object)heldSummary == (Object)(object)skip) { ClearHold(); } } internal IEnumerator WrapCountdown(AllPlayersTriggerZone zone, IEnumerator native) { //IL_005f: 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_0067: Unknown result type (might be due to invalid IL or missing references) if (disposed || native == null || !NetworkServer.active || !IsVehicleZone(zone)) { return native; } if (!FeaturesEnabled()) { RestoreDelay(zone); return native; } CountdownAttempt countdownAttempt = new CountdownAttempt { Host = (NetworkConnectionToClient)(object)NetworkServer.localConnection }; Scene activeScene = SceneManager.GetActiveScene(); countdownAttempt.Scene = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle); CountdownAttempt attempt = countdownAttempt; attempt.Switcher = DepartureSwitcher(zone); attempt.InitiallyInteractable = (Object)(object)attempt.Switcher != (Object)null && (bool)SwitcherInteractable.GetValue(attempt.Switcher); return new RoundTravelCountdownGuard(native, delegate { Coroutine val = (Coroutine)(((Object)(object)zone == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if (attempt.Handle == null && val != null) { attempt.Handle = val; } return CanDepart(zone, attempt.Scene); }, delegate { CancelCountdown(zone); }, delegate(Exception error) { RecoverCountdown(zone, attempt, error); }); } private void RecoverCountdown(AllPlayersTriggerZone zone, CountdownAttempt attempt, Exception error) { //IL_0012: 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_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Invalid comparison between Unknown and I4 //IL_00ca: Unknown result type (might be due to invalid IL or missing references) //IL_00d5: Unknown result type (might be due to invalid IL or missing references) //IL_0163: Unknown result type (might be due to invalid IL or missing references) //IL_0169: Invalid comparison between Unknown and I4 //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_0195: Unknown result type (might be due to invalid IL or missing references) //IL_0199: Unknown result type (might be due to invalid IL or missing references) //IL_01a4: Unknown result type (might be due to invalid IL or missing references) Report("limo departure callback", error); try { GameManager val = CurrentManager(); Scene activeScene = SceneManager.GetActiveScene(); bool appropriatePhase = (Object)(object)val != (Object)null && ((((Scene)(ref activeScene)).name == "HomeScene" && (int)val.state == 0) || (((Scene)(ref activeScene)).name == "CasinoScene" && (int)val.state == 1)); Coroutine val2 = (Coroutine)(((Object)(object)zone == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if (attempt.Handle == null && val2 != null) { attempt.Handle = val2; } if (!RoundTravelPolicy.CanRecoverCountdown(!disposed && FeaturesEnabled(), NetworkServer.active, object.ReferenceEquals(attempt.Host, NetworkServer.localConnection) && attempt.Host != null, ((Scene)(ref activeScene)).handle == SceneHandle.op_Implicit(attempt.Scene), (Object)(object)zone != (Object)null && ((NetworkBehaviour)zone).isServer && zone.IsActive && IsVehicleZone(zone), attempt.Handle != null && object.ReferenceEquals(val2, attempt.Handle), appropriatePhase, (Object)(object)val == (Object)null || (bool)Transitioning.GetValue(val), NetworkServer.isLoadingScene)) { return; } SceneSwitcher val3 = DepartureSwitcher(zone); bool flag = ((Scene)(ref activeScene)).name == "HomeScene" && (int)val.state == 0; int ownedTarget; if ((Object)(object)val3 != (Object)null && object.ReferenceEquals(val3, attempt.Switcher)) { Scene scene = ((Component)val3).gameObject.scene; ownedTarget = ((((Scene)(ref scene)).handle == SceneHandle.op_Implicit(attempt.Scene)) ? 1 : 0); } else { ownedTarget = 0; } if (RoundTravelPolicy.CanRecoverProgression((byte)ownedTarget != 0, attempt.InitiallyInteractable, flag, (Object)(object)val3 != (Object)null && (bool)SwitcherInteractable.GetValue(val3))) { if (flag) { SwitcherInteractable.SetValue(val3, true); } Triggered.SetValue(zone, false); Countdown.SetValue(zone, null); try { CountdownText.Invoke(zone, new object[1] { false }); } catch (Exception error2) { Report("limo recovery presentation", error2); } Write("Native limo departure failed before transition acceptance; the same departure zone can retry."); } } catch (Exception error3) { Report("limo departure recovery", error3); } } private static SceneSwitcher DepartureSwitcher(AllPlayersTriggerZone zone) { if ((Object)(object)zone == (Object)null) { return null; } object? value = DepartureEvent.GetValue(zone); UnityEvent val = (UnityEvent)((value is UnityEvent) ? value : null); if (val == null || ((UnityEventBase)val).GetPersistentEventCount() > 8) { return null; } SceneSwitcher val2 = null; for (int i = 0; i < ((UnityEventBase)val).GetPersistentEventCount(); i++) { string persistentMethodName = ((UnityEventBase)val).GetPersistentMethodName(i); if (persistentMethodName == "ServerProgressSceneWithoutInteraction") { Object persistentTarget = ((UnityEventBase)val).GetPersistentTarget(i); SceneSwitcher val3 = (SceneSwitcher)(object)((persistentTarget is SceneSwitcher) ? persistentTarget : null); if ((Object)(object)val3 == (Object)null || (Object)(object)val2 != (Object)null) { return null; } val2 = val3; } else if (persistentMethodName != "TriggerParameters" && persistentMethodName != "Stop" && persistentMethodName != "PlayOneShotWith3DPos") { return null; } } return val2; } private bool CanDepart(AllPlayersTriggerZone zone, int sceneHandle) { //IL_0029: 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_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: 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_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Invalid comparison between Unknown and I4 if (!disposed && !((Object)(object)zone == (Object)null) && ((NetworkBehaviour)zone).isServer && zone.IsActive && FeaturesEnabled()) { Scene activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).handle != SceneHandle.op_Implicit(sceneHandle))) { GameManager val = CurrentManager(); Scene activeScene2 = SceneManager.GetActiveScene(); string name = ((Scene)(ref activeScene2)).name; if ((Object)(object)val == (Object)null || ((!(name == "HomeScene") || (int)val.state != 0) && (!(name == "CasinoScene") || (int)val.state != 1))) { return false; } if (TryParty(out var party) && NativeSceneReady(val, party)) { return WagersIdle(); } return false; } } return false; } private void CancelCountdown(AllPlayersTriggerZone zone) { if ((Object)(object)zone == (Object)null || !NetworkServer.active) { return; } try { Countdown.SetValue(zone, null); CountdownText.Invoke(zone, new object[1] { false }); } catch (Exception error) { Report("limo countdown cancellation", error); } } internal void CountdownPresentation(AllPlayersTriggerZone zone) { if (disposed) { return; } try { foreach (DoorState door in doors) { if ((Object)(object)door.Zone == (Object)(object)zone) { ApplyDelay(door, FeaturesEnabled()); break; } } } catch (Exception error) { Report("limo countdown presentation", error); } } private bool TryParty(out List party) { try { return TryPartyCore(out party); } catch (Exception error) { party = null; Report("travel party guard", error); return false; } } private bool TryPartyCore(out List party) { party = null; if (!NetworkServer.active || NetworkServer.localConnection == null) { return false; } List list = readyParty(); if (list == null || list.Count == 0 || list.Count > 32 || list.Count != NetworkServer.connections.Count) { return false; } object? value = LocalInstance.GetValue(null); LocalManager val = (LocalManager)((value is LocalManager) ? value : null); if ((Object)(object)val == (Object)null || val.players == null || val.players.Count != list.Count) { return false; } List list2 = new List(); List list3 = new List(); foreach (NetworkConnectionToClient item in list) { if (item == null || !((NetworkConnection)item).isAuthenticated || !((NetworkConnection)item).isReady || (Object)(object)((NetworkConnection)item).identity == (Object)null || !NetworkServer.connections.TryGetValue(item.connectionId, out var value2) || !object.ReferenceEquals(value2, item) || !object.ReferenceEquals(((NetworkConnection)item).identity.connectionToClient, item) || ((NetworkConnection)item).identity.netId == 0 || !NetworkServer.spawned.TryGetValue(((NetworkConnection)item).identity.netId, out var value3) || !object.ReferenceEquals(value3, ((NetworkConnection)item).identity)) { return false; } list2.Add(item.connectionId); } foreach (PlayerReferences player in val.players) { if (player == null || (Object)(object)player.identity == (Object)null || (Object)(object)player.profile == (Object)null) { return false; } NetworkConnectionToClient connectionToClient = player.identity.connectionToClient; if (connectionToClient == null || !list.Contains(connectionToClient) || !object.ReferenceEquals(((NetworkConnection)connectionToClient).identity, player.identity)) { return false; } list3.Add(connectionToClient.connectionId); } if (!RoundTravelPolicy.SameParty(list2, list3, ((NetworkConnectionToClient)NetworkServer.localConnection).connectionId)) { return false; } party = list; return true; } private static bool NativeSceneReady(GameManager manager, List party) { if ((Object)(object)manager == (Object)null || party == null || (bool)Transitioning.GetValue(manager)) { return false; } HashSet hashSet = SceneReady.GetValue(manager) as HashSet; int num = (int)ExpectedPlayers.GetValue(manager); if (hashSet == null || num <= 0 || hashSet.Count < party.Count) { return false; } foreach (NetworkConnectionToClient item in party) { if (!hashSet.Contains(item.connectionId)) { return false; } } return true; } private static bool IsDaySummary(GameManager manager, SkipUI skip) { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003e: Invalid comparison between Unknown and I4 //IL_0048: 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) DaySummaryUI val = (DaySummaryUI)(((Object)(object)manager == (Object)null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)manager != (Object)null && (Object)(object)skip != (Object)null && ((NetworkBehaviour)manager).isServer && (int)manager.state == 1 && manager.HasDayStarted) { Scene activeScene = SceneManager.GetActiveScene(); if (((Scene)(ref activeScene)).name == "CasinoScene" && (Object)(object)val != (Object)null && ((Component)val).gameObject.activeInHierarchy && object.ReferenceEquals(SummarySkip.GetValue(val), skip)) { return !(bool)Transitioning.GetValue(manager); } } return false; } private bool IsVehicleZone(AllPlayersTriggerZone zone) { foreach (DoorState door in doors) { if ((Object)(object)door.Doors != (Object)null && (Object)(object)door.Zone == (Object)(object)zone) { return true; } } return false; } private void ApplyDelay(DoorState state, bool enabled) { if ((Object)(object)state.Zone == (Object)null) { return; } if (state.Owned && !RoundTravelPolicy.OwnsDelay(GetDelay(state.Zone), 1f)) { state.Owned = false; state.ChangedExternally = true; state.AppliedCountdownText = null; } if (!enabled) { if (state.Owned) { DepartureDelay.SetValue(state.Zone, state.PreviousDelay); RestoreCountdownLabel(state); state.Owned = false; } } else if (!state.Owned && !state.ChangedExternally) { float delay = GetDelay(state.Zone); if (!float.IsNaN(delay) && !float.IsInfinity(delay) && !(delay <= 0f)) { state.PreviousDelay = delay; DepartureDelay.SetValue(state.Zone, 1f); state.Owned = true; PrimeCountdownLabel(state); } } } private static void PrimeCountdownLabel(DoorState state) { if (!((Object)(object)state.CountdownLabel == (Object)null) && CountdownTextRoutine.GetValue(state.Zone) == null) { state.PreviousCountdownText = state.CountdownLabel.text; state.AppliedCountdownText = RoundTravelPolicy.IdleCountdownText(GetDelay(state.Zone)); if (state.AppliedCountdownText != null) { state.CountdownLabel.text = state.AppliedCountdownText; } } } private static void RestoreCountdownLabel(DoorState state) { if ((Object)(object)state.CountdownLabel != (Object)null && state.AppliedCountdownText != null && CountdownTextRoutine.GetValue(state.Zone) == null && string.Equals(state.CountdownLabel.text, state.AppliedCountdownText, StringComparison.Ordinal)) { state.CountdownLabel.text = state.PreviousCountdownText; } state.AppliedCountdownText = null; } private void RestoreDelay(AllPlayersTriggerZone zone) { foreach (DoorState door in doors) { if ((Object)(object)door.Zone == (Object)(object)zone) { ApplyDelay(door, enabled: false); break; } } } private bool FeaturesEnabled() { if (disposed) { return false; } try { return canUse(); } catch (Exception error) { Report("travel feature guard", error); return false; } } private bool WagersIdle() { try { return wagersIdle(); } catch (Exception error) { Report("travel wager guard", error); return false; } } private static GameManager CurrentManager() { object? value = ManagerInstance.GetValue(null); return (GameManager)((value is GameManager) ? value : null); } private static float GetDelay(AllPlayersTriggerZone zone) { return (float)DepartureDelay.GetValue(zone); } private void ClearHold() { heldSummary = null; heldHost = null; } private void Write(string message) { if (log != null) { try { log(message); } catch { } } } private void Report(string operation, Exception error) { if (reportedErrors.Count < 8 && reportedErrors.Add(operation)) { Write("Native " + operation + " held: " + error.GetBaseException().Message); } } private static bool NativeApiAvailable() { if (ManagerInstance != null && ManagerInstance.IsStatic && LocalInstance != null && LocalInstance.IsStatic && Transitioning != null && SceneReady != null && ExpectedPlayers != null && ManagerSummary != null && SummarySkip != null && Skippable != null && SkippableLocal != null && HasSkipped != null && SkippedPlayers != null && PhoneDoors != null && PhoneCalled != null && DoorsOpen != null && DoorTrigger != null && DepartureDelay != null && Countdown != null && Triggered != null && DepartureEvent != null && SwitcherInteractable != null && CountdownLabel != null && CountdownTextRoutine != null && CallPhone != null) { return CountdownText != null; } return false; } public static string SelfTest() { if (!NativeApiAvailable()) { throw new InvalidOperationException("Native travel API shape changed."); } if (RoundTravelPolicy.CanReady(enabled: true, server: true, roundSummary: true, skippable: true, completedHostHold: true, nativeHostReady: true, sceneReady: true, wagersIdle: true) && !RoundTravelPolicy.CanReady(enabled: true, server: true, roundSummary: false, skippable: true, completedHostHold: true, nativeHostReady: true, sceneReady: true, wagersIdle: true) && !RoundTravelPolicy.CanReady(enabled: true, server: true, roundSummary: true, skippable: true, completedHostHold: true, nativeHostReady: true, sceneReady: false, wagersIdle: true) && !RoundTravelPolicy.CanReady(enabled: true, server: true, roundSummary: true, skippable: true, completedHostHold: true, nativeHostReady: true, sceneReady: true, wagersIdle: false) && RoundTravelPolicy.SameParty(new int[2] { 0, 2 }, new int[2] { 2, 0 }, 0)) { int[] connections = new int[2] { 0, 2 }; int[] nativePlayers = new int[2]; if (!RoundTravelPolicy.SameParty(connections, nativePlayers, 0)) { return "PASS: native travel API; host summary hold, exact party, load/wager fences and delay ownership policy (offline)"; } } throw new InvalidOperationException("Native travel policy failed."); } public void Dispose() { if (disposed) { return; } foreach (DoorState door in doors) { if ((Object)(object)door.Zone != (Object)null && door.Owned && RoundTravelPolicy.OwnsDelay(GetDelay(door.Zone), 1f)) { DepartureDelay.SetValue(door.Zone, door.PreviousDelay); RestoreCountdownLabel(door); } } ClearHold(); phones.Clear(); doors.Clear(); disposed = true; if (object.ReferenceEquals(Active, this)) { Active = null; } } } [HarmonyPatch(typeof(InteractableBase), "OnStartClient")] internal static class TravelPhoneSpawnPatch { private static void Postfix(InteractableBase __instance) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Expected O, but got Unknown if (ChaosRoundTravel.Active != null && __instance is PhoneBooth) { ChaosRoundTravel.Active.RegisterPhone((PhoneBooth)__instance); } } } [HarmonyPatch(typeof(VehicleDoors), "ServerOpenDoors")] internal static class TravelServerDoorsPatch { private static void Prefix(VehicleDoors __instance) { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.RegisterDoor(__instance); } } } [HarmonyPatch(typeof(VehicleDoors), "OnDoorsOpenChanged")] internal static class TravelClientDoorsPatch { private static void Prefix(VehicleDoors __instance) { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.RegisterDoor(__instance); } } } [HarmonyPatch(typeof(GameManager), "UserCode_RpcSceneInitialized__Int32")] internal static class TravelSceneInitializedPatch { private static void Postfix() { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.SceneInitialized(); } } } [HarmonyPatch(typeof(SkipUI), "OnSkipFilled")] internal static class TravelHostSummaryHoldPatch { private static void Prefix(SkipUI __instance) { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.HostHoldCompleted(__instance); } } } [HarmonyPatch(typeof(SkipUI), "UserCode_RequestSkip__NetworkConnectionToClient")] internal static class TravelNativeSummaryReadyPatch { private static void Postfix(SkipUI __instance, NetworkConnectionToClient __0) { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.NativeSkipAccepted(__instance, __0); } } } [HarmonyPatch(typeof(SkipUI), "Reset")] internal static class TravelSummaryResetPatch { private static void Postfix(SkipUI __instance) { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.ResetSummary(__instance); } } } [HarmonyPatch(typeof(AllPlayersTriggerZone), "CountdownRoutine")] internal static class TravelNativeCountdownPatch { private static void Postfix(AllPlayersTriggerZone __instance, ref IEnumerator __result) { if (ChaosRoundTravel.Active != null) { __result = ChaosRoundTravel.Active.WrapCountdown(__instance, __result); } } } [HarmonyPatch(typeof(AllPlayersTriggerZone), "UserCode_RpcUpdateCountdownText__Boolean")] internal static class TravelCountdownTextPatch { private static void Prefix(AllPlayersTriggerZone __instance) { if (ChaosRoundTravel.Active != null) { ChaosRoundTravel.Active.CountdownPresentation(__instance); } } } [Serializable] public class ChaosCheckpoint { public int Schema = 1; public string Catalog = "chaosnight-0.1.0-r1"; public string NativeSaveName; public string NativeHash; public string NativeJson; public string EngineJson; public string RulesJson; public double QuotaMantissa; public long QuotaExponent; public double AdjustedQuotaMantissa; public long AdjustedQuotaExponent; public bool QuotaAdjusted; public float OriginalDuration; public int NativeDays; public int NativeQuotas; public long Revision; public string PreDayEngineJson; public long DayStartTickets; public long StagedShopSpend; public long PreDayShopSpend; public bool DaySuspended; public int SkippedNativeDay = -1; public bool HasNativeBaseline; public double NativeStartBalanceMantissa; public long NativeStartBalanceExponent; public long NativeStartTickets; public bool HasCashDeliveryBasis; public double CashDeliveryMantissa; public long CashDeliveryExponent; public string DayStartNativeJson; public int DayStartNativeDay; public string PoliceBailJson; } [Serializable] public sealed class ChaosRecoveryFile { public string Name; public string SourcePath; public long Bytes; public string Sha256; } [Serializable] public sealed class ChaosRecoveryManifest { public int Schema = 1; public string OriginalSaveName; public string NativeSavePath; public string CreatedUtc; public List Files = new List(); } public sealed class ChaosSave { private sealed class RecoverySource { public string Name; public string Path; public byte[] Data; } private readonly string directory; public ChaosSave(string root) { directory = Path.Combine(root, "ChaosNight", "checkpoints"); } public static string Hash(string text) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Encoding.UTF8.GetBytes(text)); StringBuilder stringBuilder = new StringBuilder(64); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2")); } return stringBuilder.ToString(); } public static string HashBytes(byte[] data) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(data); StringBuilder stringBuilder = new StringBuilder(64); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2")); } return stringBuilder.ToString(); } public string CreateRecoveryBackup(string saveName, string nativePath, string liveNativeJson, string recoveryRoot) { if (string.IsNullOrWhiteSpace(saveName) || saveName == "." || saveName == ".." || saveName.IndexOfAny(Path.GetInvalidFileNameChars()) >= 0 || saveName != Path.GetFileName(saveName)) { throw new ArgumentException("Recovery backup needs a plain native save name."); } if (string.IsNullOrWhiteSpace(nativePath) || !Path.IsPathRooted(nativePath) || string.IsNullOrWhiteSpace(recoveryRoot) || string.IsNullOrWhiteSpace(liveNativeJson)) { throw new ArgumentException("Recovery backup needs an absolute native path, live snapshot and destination."); } string fullPath = Path.GetFullPath(nativePath); if (!string.Equals(Path.GetFileName(fullPath), saveName + ".json", StringComparison.OrdinalIgnoreCase)) { throw new ArgumentException("Native save filename does not match its selected save name."); } if (!File.Exists(fullPath)) { throw new FileNotFoundException("Existing native save must be preserved before a fresh run.", fullPath); } List list = new List(); list.Add(ReadRecoverySource("native-save.json", fullPath)); string fullPath2 = Path.GetFullPath(PathFor(saveName)); if (File.Exists(fullPath2)) { list.Add(ReadRecoverySource("chaos-checkpoint.json", fullPath2)); } if (File.Exists(fullPath2 + ".previous")) { list.Add(ReadRecoverySource("chaos-checkpoint.previous.json", fullPath2 + ".previous")); } list.Add(new RecoverySource { Name = "live-state.json", Path = "", Data = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(liveNativeJson) }); DateTime utcNow = DateTime.UtcNow; string text = Path.Combine(Path.GetFullPath(recoveryRoot), utcNow.ToString("yyyyMMddTHHmmssfffZ", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N")); if (Directory.Exists(text) || File.Exists(text)) { throw new IOException("Recovery backup destination already exists."); } Directory.CreateDirectory(text); ChaosRecoveryManifest chaosRecoveryManifest = new ChaosRecoveryManifest(); chaosRecoveryManifest.OriginalSaveName = saveName; chaosRecoveryManifest.NativeSavePath = fullPath; chaosRecoveryManifest.CreatedUtc = utcNow.ToString("o", CultureInfo.InvariantCulture); ChaosRecoveryManifest chaosRecoveryManifest2 = chaosRecoveryManifest; foreach (RecoverySource item in list) { string path = Path.Combine(text, item.Name); WriteNewFile(path, item.Data); string text2 = HashBytes(item.Data); if (HashBytes(File.ReadAllBytes(path)) != text2) { throw new IOException("Recovery backup failed readback verification: " + item.Name); } chaosRecoveryManifest2.Files.Add(new ChaosRecoveryFile { Name = item.Name, SourcePath = item.Path, Bytes = item.Data.LongLength, Sha256 = text2 }); } byte[] bytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false).GetBytes(ChaosJson.Write(chaosRecoveryManifest2)); string path2 = Path.Combine(text, "manifest.json"); WriteNewFile(path2, bytes); if (HashBytes(File.ReadAllBytes(path2)) != HashBytes(bytes)) { throw new IOException("Recovery manifest failed readback verification."); } foreach (RecoverySource item2 in list) { if (item2.Path.Length > 0 && HashBytes(File.ReadAllBytes(item2.Path)) != HashBytes(item2.Data)) { throw new IOException("Original save changed while its recovery backup was being prepared."); } } return text; } private static RecoverySource ReadRecoverySource(string name, string path) { RecoverySource recoverySource = new RecoverySource(); recoverySource.Name = name; recoverySource.Path = path; recoverySource.Data = File.ReadAllBytes(path); return recoverySource; } private static void WriteNewFile(string path, byte[] bytes) { using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(); } private string PathFor(string saveName) { return Path.Combine(directory, Hash(saveName) + ".json"); } public void Write(ChaosCheckpoint checkpoint) { Directory.CreateDirectory(directory); string text = PathFor(checkpoint.NativeSaveName); string text2 = text + ".pending"; File.WriteAllText(text2, ChaosJson.Write(checkpoint), new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); if (File.Exists(text)) { File.Replace(text2, text, text + ".previous", ignoreMetadataErrors: true); } else { File.Move(text2, text); } } public ChaosCheckpoint Load(string saveName, string nativeJson, out string reason) { reason = null; string text = PathFor(saveName); if (!File.Exists(text)) { return null; } string[] array = new string[2] { text, text + ".previous" }; foreach (string path in array) { if (!File.Exists(path)) { continue; } try { ChaosCheckpoint chaosCheckpoint = ChaosJson.Read(File.ReadAllText(path)); if (chaosCheckpoint != null && chaosCheckpoint.Schema == 1 && chaosCheckpoint.Catalog == "chaosnight-0.1.0-r1" && chaosCheckpoint.NativeSaveName == saveName && chaosCheckpoint.NativeHash == Hash(nativeJson)) { return chaosCheckpoint; } } catch (Exception) { } } reason = "Chaos checkpoint does not match this native save. Chaos is held for this session; a paired backup is retained."; return null; } } } namespace ChaosNight.Core { public enum SessionIdentityStatus { Ready, ConnectionMissing, ConnectionReplaced, NotAuthenticated, NotReady, IdentityMissing, IdentityOwnershipMismatch, IdentityNotSpawned, ProfileMissing, ProfileIdentityMismatch, ProfileSteamIdMissing, LocalPlayerMismatch, LocalProfileNotSynced, LocalSteamIdMissing, SteamIdMismatch, UnsupportedTransport, SteamPeerMissing, NoPlayers, TooManyPlayers, DuplicateSteamId, HostMissing, MultipleHosts } public sealed class SessionPlayerIdentity { public bool HasConnection; public bool LiveConnection; public bool Authenticated; public bool Ready; public bool HasIdentity; public bool OwnsIdentity; public bool SpawnedIdentity; public bool HasProfile; public bool ProfileOwnsIdentity; public bool IsLocal; public bool LocalPlayerMatches; public bool ProfileHasSynced; public bool SteamTransport; public ulong ProfileSteamId; public ulong LocalSteamId; public string RemoteSteamAddress; } public static class SessionIdentity { public const int MaximumPlayers = 32; public static bool TrySteamAddress(string address, out ulong steamId) { steamId = 0uL; if (string.IsNullOrEmpty(address) || address.Length > 20 || address[0] == '0') { return false; } foreach (char c in address) { if (c < '0' || c > '9') { return false; } } if (ulong.TryParse(address, NumberStyles.None, CultureInfo.InvariantCulture, out steamId)) { return steamId != 0; } return false; } public static bool TryResolve(SessionPlayerIdentity player, out ulong steamId) { SessionIdentityStatus status; return TryResolve(player, out steamId, out status); } public static bool TryResolve(SessionPlayerIdentity player, out ulong steamId, out SessionIdentityStatus status) { steamId = 0uL; status = SessionIdentityStatus.Ready; if (player == null || !player.HasConnection) { status = SessionIdentityStatus.ConnectionMissing; } else if (!player.LiveConnection) { status = SessionIdentityStatus.ConnectionReplaced; } else if (!player.Authenticated) { status = SessionIdentityStatus.NotAuthenticated; } else if (!player.Ready) { status = SessionIdentityStatus.NotReady; } else if (!player.HasIdentity) { status = SessionIdentityStatus.IdentityMissing; } else if (!player.OwnsIdentity) { status = SessionIdentityStatus.IdentityOwnershipMismatch; } else if (!player.SpawnedIdentity) { status = SessionIdentityStatus.IdentityNotSpawned; } else if (!player.HasProfile) { status = SessionIdentityStatus.ProfileMissing; } else if (!player.ProfileOwnsIdentity) { status = SessionIdentityStatus.ProfileIdentityMismatch; } else if (player.ProfileSteamId == 0) { status = SessionIdentityStatus.ProfileSteamIdMissing; } if (status != SessionIdentityStatus.Ready) { return false; } if (player.IsLocal) { if (!player.LocalPlayerMatches) { status = SessionIdentityStatus.LocalPlayerMismatch; } else if (!player.ProfileHasSynced) { status = SessionIdentityStatus.LocalProfileNotSynced; } else if (player.LocalSteamId == 0) { status = SessionIdentityStatus.LocalSteamIdMissing; } else if (player.ProfileSteamId != player.LocalSteamId) { status = SessionIdentityStatus.SteamIdMismatch; } if (status != SessionIdentityStatus.Ready) { return false; } steamId = player.LocalSteamId; return true; } if (!player.SteamTransport) { status = SessionIdentityStatus.UnsupportedTransport; return false; } if (!TrySteamAddress(player.RemoteSteamAddress, out var steamId2)) { status = SessionIdentityStatus.SteamPeerMissing; return false; } if (steamId2 != player.ProfileSteamId) { status = SessionIdentityStatus.SteamIdMismatch; return false; } steamId = steamId2; return true; } public static bool TryRoster(IList connections, int maximum, out ulong[] players) { SessionIdentityStatus status; return TryRoster(connections, maximum, out players, out status); } public static bool TryRoster(IList connections, int maximum, out ulong[] players, out SessionIdentityStatus status) { players = new ulong[0]; status = SessionIdentityStatus.Ready; if (connections == null || connections.Count == 0) { status = SessionIdentityStatus.NoPlayers; return false; } if (maximum < 1 || maximum > 32 || connections.Count > maximum) { status = SessionIdentityStatus.TooManyPlayers; return false; } List list = new List(); int num = 0; foreach (SessionPlayerIdentity connection in connections) { if (!TryResolve(connection, out var steamId, out status)) { return false; } if (list.Contains(steamId)) { status = SessionIdentityStatus.DuplicateSteamId; return false; } if (connection.IsLocal) { num++; } list.Add(steamId); } if (num != 1) { status = ((num == 0) ? SessionIdentityStatus.HostMissing : SessionIdentityStatus.MultipleHosts); return false; } list.Sort(); players = list.ToArray(); return true; } public static string HoldMessage(SessionIdentityStatus status) { switch (status) { case SessionIdentityStatus.Ready: return null; case SessionIdentityStatus.NotAuthenticated: return "Wait for every player's connection to authenticate."; case SessionIdentityStatus.NotReady: return "Wait for every player to finish loading the scene."; case SessionIdentityStatus.ProfileSteamIdMissing: return "A player is still loading their Steam profile."; case SessionIdentityStatus.SteamIdMismatch: return "A player's Steam profile does not match their connection. Reconnect that player."; case SessionIdentityStatus.UnsupportedTransport: return "The current connection cannot verify Steam player identities."; case SessionIdentityStatus.SteamPeerMissing: return "A player's Steam connection has no verified ID. Reconnect that player."; case SessionIdentityStatus.LocalProfileNotSynced: return "Wait for the host's Steam profile to finish syncing."; case SessionIdentityStatus.LocalSteamIdMissing: return "The host's Steam account is not ready yet."; case SessionIdentityStatus.DuplicateSteamId: return "Two player connections share one Steam ID. Reconnect those players."; case SessionIdentityStatus.TooManyPlayers: return "The party exceeds the supported session-control limit."; case SessionIdentityStatus.ConnectionMissing: case SessionIdentityStatus.ConnectionReplaced: return "A player's connection changed. Wait for them to reconnect."; case SessionIdentityStatus.IdentityMissing: case SessionIdentityStatus.ProfileMissing: return "Wait for every player's character to finish loading."; case SessionIdentityStatus.IdentityOwnershipMismatch: case SessionIdentityStatus.IdentityNotSpawned: case SessionIdentityStatus.ProfileIdentityMismatch: return "A player's character is not bound to their current connection. Reconnect that player."; default: return "Wait for the host's local player to finish loading."; } } } public static class SessionInput { public const int MinimumDay = 1; public const int MaximumDay = 9999; public const int MaximumCashTextLength = 48; public static bool TryDay(string text, out int day) { day = 0; if (string.IsNullOrWhiteSpace(text)) { return false; } if (!int.TryParse(text.Trim(), NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result < 1 || result > 9999) { return false; } day = result; return true; } public static bool TryPreviousDayQuotaStep(int day, int daysBeforeQuota, out int step) { step = 0; if (day <= 1 || day > 9999 || daysBeforeQuota <= 0) { return false; } step = (day - 2) / daysBeforeQuota; return true; } public static bool TryCashText(double mantissa, long exponent, out string text) { text = null; if (double.IsNaN(mantissa) || double.IsInfinity(mantissa) || mantissa < 0.0) { return false; } string text2 = ((mantissa == 0.0) ? "0" : (mantissa.ToString("R", CultureInfo.InvariantCulture) + "e" + exponent.ToString(CultureInfo.InvariantCulture))); if (!TryCash(text2, out var _, out var _)) { return false; } text = text2; return true; } public static bool TryCash(string text, out double mantissa, out long exponent) { mantissa = 0.0; exponent = 0L; if (string.IsNullOrWhiteSpace(text) || text.Length > 48) { return false; } string text2 = text.Trim(); if (text2[0] == '$') { text2 = text2.Substring(1).Trim(); } if (text2.Length == 0) { return false; } int num = SuffixPower(text2[text2.Length - 1]); if (num > 0) { text2 = text2.Substring(0, text2.Length - 1); } if (text2.Length == 0) { return false; } int num2 = -1; for (int i = 0; i < text2.Length; i++) { if (text2[i] == 'e' || text2[i] == 'E') { if (num2 >= 0 || num > 0) { return false; } num2 = i; } } long result = 0L; if (num2 >= 0) { string text3 = text2.Substring(num2 + 1); if (text3.Length == 0 || !long.TryParse(text3, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out result)) { return false; } text2 = text2.Substring(0, num2); } int num3 = text2.IndexOf('.'); if (num3 >= 0 && text2.IndexOf('.', num3 + 1) >= 0) { return false; } string text4 = ((num3 < 0) ? text2 : text2.Substring(0, num3)); string text5 = ((num3 < 0) ? "" : text2.Substring(num3 + 1)); if (text4.Length == 0 && text5.Length == 0) { return false; } if (!ValidInteger(text4) || !Digits(text5)) { return false; } text4 = text4.Replace(",", ""); string text6 = text4 + text5; int j; for (j = 0; j < text6.Length && text6[j] == '0'; j++) { } if (j == text6.Length) { return true; } long num4; try { num4 = checked(result + num + text4.Length - j - 1); } catch (OverflowException) { return false; } if (num4 > 300) { return false; } if (num4 == 300) { if (text6[j] != '1') { return false; } for (int k = j + 1; k < text6.Length; k++) { if (text6[k] != '0') { return false; } } } string s = text6.Substring(j, 1) + ((j + 1 < text6.Length) ? ("." + text6.Substring(j + 1)) : ""); if (!double.TryParse(s, NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out var result2) || double.IsNaN(result2) || double.IsInfinity(result2) || result2 <= 0.0) { return false; } if (result2 >= 10.0) { result2 /= 10.0; num4++; } if (num4 > 300 || (num4 == 300 && result2 > 1.0)) { return false; } mantissa = result2; exponent = num4; return true; } private static bool ValidInteger(string text) { if (text.IndexOf(',') < 0) { return Digits(text); } string[] array = text.Split(','); if (array[0].Length < 1 || array[0].Length > 3 || !Digits(array[0])) { return false; } for (int i = 1; i < array.Length; i++) { if (array[i].Length != 3 || !Digits(array[i])) { return false; } } return true; } private static bool Digits(string text) { foreach (char c in text) { if (c < '0' || c > '9') { return false; } } return true; } private static int SuffixPower(char suffix) { return char.ToUpperInvariant(suffix) switch { 'K' => 3, 'M' => 6, 'B' => 9, 'T' => 12, 'Q' => 15, _ => 0, }; } } } namespace ChaosNight { public sealed class ChaosStatsArt : IDisposable { private sealed class Illustration { public readonly Color32[] Pixels = (Color32[])(object)new Color32[64000]; private readonly Color Ink = Shade(23, 34, 50); private readonly Color Soft = Shade(48, 68, 88); private readonly Color Paper = Shade(244, 242, 225); private readonly Color Gold = Shade(234, 188, 95); private readonly Color Mint = Shade(86, 206, 176); private readonly Color Red = Shade(224, 97, 101); private static Color Shade(int red, int green, int blue) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) return new Color((float)red / 255f, (float)green / 255f, (float)blue / 255f, 1f); } public Illustration(int game) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: 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_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01d5: Unknown result type (might be due to invalid IL or missing references) //IL_00ab: Unknown result type (might be due to invalid IL or missing references) //IL_0134: 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_0147: Unknown result type (might be due to invalid IL or missing references) //IL_014c: Unknown result type (might be due to invalid IL or missing references) //IL_0151: Unknown result type (might be due to invalid IL or missing references) Color val; switch (game) { default: val = Shade(67, 58, 78); break; case 2: case 10: val = Shade(34, 73, 91); break; case 5: case 8: val = Shade(35, 83, 71); break; } Color val2 = val; for (int i = 0; i < 200; i++) { for (int j = 0; j < 320; j++) { float num = (float)j / 2f; float num2 = (float)i / 2f; float num3 = Mathf.Sqrt((num - 80f) * (num - 80f) / 8500f + (num2 - 58f) * (num2 - 58f) / 4600f); ref Color32 reference = ref Pixels[i * 320 + j]; reference = Color32.op_Implicit(Color.Lerp(val2, Shade(13, 22, 34), Mathf.Clamp01(num3))); } } Ellipse(80f, 13f, 56f, 8f, new Color(0f, 0f, 0f, 0.25f)); Line(20f, 14f, 140f, 14f, 0.6f, new Color(0.55f, 0.69f, 0.8f, 0.2f)); } private void Paint(int x, int y, Color color, float coverage) { //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_002a: 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_002c: Unknown result type (might be due to invalid IL or missing references) //IL_003c: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_0060: Unknown result type (might be due to invalid IL or missing references) if (!(coverage <= 0f)) { int num = y * 320 + x; Color val = Color32.op_Implicit(Pixels[num]); Color val2 = Color.Lerp(val, color, Mathf.Clamp01(coverage) * color.a); val2.a = 1f; ref Color32 reference = ref Pixels[num]; reference = Color32.op_Implicit(val2); } } private void Rect(float x, float y, float width, float height, float radius, Color color) { //IL_0123: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, (int)((x - 1f) * 2f)); int num2 = Math.Min(319, (int)((x + width + 1f) * 2f)); int num3 = Math.Max(0, (int)((y - 1f) * 2f)); int num4 = Math.Min(199, (int)((y + height + 1f) * 2f)); for (int i = num3; i <= num4; i++) { for (int j = num; j <= num2; j++) { float num5 = Mathf.Abs(((float)j + 0.5f) / 2f - x - width / 2f) - width / 2f + radius; float num6 = Mathf.Abs(((float)i + 0.5f) / 2f - y - height / 2f) - height / 2f + radius; float num7 = Mathf.Sqrt(Mathf.Max(num5, 0f) * Mathf.Max(num5, 0f) + Mathf.Max(num6, 0f) * Mathf.Max(num6, 0f)); float num8 = num7 + Mathf.Min(Mathf.Max(num5, num6), 0f) - radius; Paint(j, i, color, 0.5f - num8 * 2f); } } } private void Ellipse(float x, float y, float rx, float ry, Color color) { //IL_00c1: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, (int)((x - rx - 1f) * 2f)); int num2 = Math.Min(319, (int)((x + rx + 1f) * 2f)); int num3 = Math.Max(0, (int)((y - ry - 1f) * 2f)); int num4 = Math.Min(199, (int)((y + ry + 1f) * 2f)); for (int i = num3; i <= num4; i++) { for (int j = num; j <= num2; j++) { float num5 = (((float)j + 0.5f) / 2f - x) / rx; float num6 = (((float)i + 0.5f) / 2f - y) / ry; float num7 = (Mathf.Sqrt(num5 * num5 + num6 * num6) - 1f) * Mathf.Min(rx, ry); Paint(j, i, color, 0.5f - num7 * 2f); } } } private void Circle(float x, float y, float radius, Color color) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) Ellipse(x, y, radius, radius, color); } private void Line(float x1, float y1, float x2, float y2, float thickness, Color color) { //IL_010e: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, (int)((Math.Min(x1, x2) - thickness - 1f) * 2f)); int num2 = Math.Min(319, (int)((Math.Max(x1, x2) + thickness + 1f) * 2f)); int num3 = Math.Max(0, (int)((Math.Min(y1, y2) - thickness - 1f) * 2f)); int num4 = Math.Min(199, (int)((Math.Max(y1, y2) + thickness + 1f) * 2f)); float num5 = x2 - x1; float num6 = y2 - y1; float num7 = num5 * num5 + num6 * num6; for (int i = num3; i <= num4; i++) { for (int j = num; j <= num2; j++) { float num8 = ((float)j + 0.5f) / 2f - x1; float num9 = ((float)i + 0.5f) / 2f - y1; float num10 = ((num7 == 0f) ? 0f : Mathf.Clamp01((num8 * num5 + num9 * num6) / num7)); float num11 = num8 - num5 * num10; float num12 = num9 - num6 * num10; Paint(j, i, color, 0.5f - (Mathf.Sqrt(num11 * num11 + num12 * num12) - thickness / 2f) * 2f); } } } private void Triangle(float x1, float y1, float x2, float y2, float x3, float y3, Color color) { //IL_0129: Unknown result type (might be due to invalid IL or missing references) int num = Math.Max(0, (int)(Math.Min(x1, Math.Min(x2, x3)) * 2f)); int num2 = Math.Min(319, (int)(Math.Max(x1, Math.Max(x2, x3)) * 2f)); int num3 = Math.Max(0, (int)(Math.Min(y1, Math.Min(y2, y3)) * 2f)); int num4 = Math.Min(199, (int)(Math.Max(y1, Math.Max(y2, y3)) * 2f)); for (int i = num3; i <= num4; i++) { for (int j = num; j <= num2; j++) { float num5 = ((float)j + 0.5f) / 2f; float num6 = ((float)i + 0.5f) / 2f; float num7 = (num5 - x2) * (y1 - y2) - (x1 - x2) * (num6 - y2); float num8 = (num5 - x3) * (y2 - y3) - (x2 - x3) * (num6 - y3); float num9 = (num5 - x1) * (y3 - y1) - (x3 - x1) * (num6 - y1); if ((!(num7 < 0f) && !(num8 < 0f) && !(num9 < 0f)) || (!(num7 > 0f) && !(num8 > 0f) && !(num9 > 0f))) { Paint(j, i, color, 1f); } } } } private void Panel(float x, float y, float width, float height) { //IL_002b: 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_007a: Unknown result type (might be due to invalid IL or missing references) Rect(x + 1f, y - 3f, width, height, 7f, new Color(0f, 0f, 0f, 0.3f)); Rect(x, y, width, height, 7f, Shade(113, 138, 154)); Rect(x + 1.5f, y + 1.5f, width - 3f, height - 3f, 6f, Ink); } private void Chip(float x, float y, float radius, Color color) { //IL_0010: 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_0030: 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_0099: Unknown result type (might be due to invalid IL or missing references) Circle(x, y - 2f, radius, Shade(12, 19, 27)); Circle(x, y, radius, color); Circle(x, y, radius * 0.72f, Paper); Circle(x, y, radius * 0.55f, color); for (int i = 0; i < 8; i++) { float num = (float)i * (float)Math.PI / 4f; Line(x + Mathf.Cos(num) * radius * 0.82f, y + Mathf.Sin(num) * radius * 0.82f, x + Mathf.Cos(num) * radius, y + Mathf.Sin(num) * radius, 1.4f, Paper); } } private void Suit(float x, float y, float scale, bool heart) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_00b6: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_011f: 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_0051: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) Color color = (heart ? Red : Ink); if (heart) { Circle(x - scale * 0.35f, y + scale * 0.2f, scale * 0.44f, color); Circle(x + scale * 0.35f, y + scale * 0.2f, scale * 0.44f, color); Triangle(x - scale * 0.72f, y + scale * 0.08f, x + scale * 0.72f, y + scale * 0.08f, x, y - scale * 0.8f, color); } else { Triangle(x, y + scale, x - scale * 0.75f, y - scale * 0.1f, x + scale * 0.75f, y - scale * 0.1f, color); Circle(x - scale * 0.32f, y - scale * 0.1f, scale * 0.43f, color); Circle(x + scale * 0.32f, y - scale * 0.1f, scale * 0.43f, color); Triangle(x, y - scale * 0.1f, x - scale * 0.32f, y - scale, x + scale * 0.32f, y - scale, color); } } private void Card(float x, float y, float width, float height, bool heart, int pips) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) Rect(x + 1f, y - 3f, width, height, 3f, new Color(0f, 0f, 0f, 0.35f)); Rect(x, y, width, height, 3f, Paper); Rect(x + 1f, y + height - 2f, width - 2f, 1f, 0.5f, Color.white); Suit(x + 6f, y + height - 7f, 2.6f, heart); Suit(x + width - 6f, y + 7f, 2.6f, heart); if (pips == 1) { Suit(x + width / 2f, y + height / 2f, width * 0.23f, heart); return; } int num = (pips + 1) / 2; for (int i = 0; i < pips; i++) { Suit(x + width * ((i % 2 == 0) ? 0.33f : 0.67f), y + height * ((num == 1) ? 0.5f : (0.27f + (float)(i / 2) * 0.46f / (float)(num - 1))), width * 0.13f, heart); } } private void Dice(float x, float y, float size, int pips) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) Rect(x + 2f, y - 3f, size, size, 4f, Shade(109, 119, 130)); Rect(x, y, size, size, 4f, Paper); for (int i = 0; i < pips; i++) { float num = ((pips == 1) ? 0.5f : ((i < 3) ? 0.27f : 0.73f)); float num2 = ((pips == 1) ? 0.5f : (0.26f + (float)(i % 3) * 0.24f)); Circle(x + num * size, y + num2 * size, size * 0.07f, Ink); } } private void Duck(float x, float y, float scale, Color color) { //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) //IL_0098: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) //IL_00ea: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) Ellipse(x, y - 7f * scale, 17f * scale, 3f * scale, new Color(0.55f, 0.87f, 0.93f, 0.3f)); Ellipse(x, y, 14f * scale, 9f * scale, color); Circle(x + 10f * scale, y + 10f * scale, 8f * scale, color); Ellipse(x - 2f * scale, y + scale, 8f * scale, 5f * scale, Color.Lerp(color, Paper, 0.2f)); Rect(x + 14f * scale, y + 7f * scale, 8f * scale, 4f * scale, scale, Gold); Circle(x + 12f * scale, y + 13f * scale, 1.2f * scale, Ink); Circle(x + 12.3f * scale, y + 13.4f * scale, 0.35f * scale, Paper); } private void Digit(int digit, float x, float y, float scale, Color color) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) int[] array = new int[10] { 63, 6, 91, 79, 102, 109, 125, 7, 127, 111 }; int num = array[digit % 10]; float[,] array2 = new float[7, 4] { { 0f, 8f, 4f, 8f }, { 4f, 8f, 4f, 4f }, { 4f, 4f, 4f, 0f }, { 0f, 0f, 4f, 0f }, { 0f, 4f, 0f, 0f }, { 0f, 8f, 0f, 4f }, { 0f, 4f, 4f, 4f } }; for (int i = 0; i < 7; i++) { if ((num & (1 << i)) != 0) { Line(x + array2[i, 0] * scale, y + array2[i, 1] * scale, x + array2[i, 2] * scale, y + array2[i, 3] * scale, scale, color); } } } private void Wheel(int game) { //IL_001b: 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_0056: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0229: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02b6: Unknown result type (might be due to invalid IL or missing references) //IL_02d1: Unknown result type (might be due to invalid IL or missing references) //IL_02ec: Unknown result type (might be due to invalid IL or missing references) //IL_030c: Unknown result type (might be due to invalid IL or missing references) //IL_0327: Unknown result type (might be due to invalid IL or missing references) //IL_0347: Unknown result type (might be due to invalid IL or missing references) //IL_0362: 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_0382: Unknown result type (might be due to invalid IL or missing references) //IL_039d: Unknown result type (might be due to invalid IL or missing references) //IL_03b8: Unknown result type (might be due to invalid IL or missing references) //IL_018f: Unknown result type (might be due to invalid IL or missing references) //IL_0194: Unknown result type (might be due to invalid IL or missing references) //IL_0182: Unknown result type (might be due to invalid IL or missing references) //IL_017a: Unknown result type (might be due to invalid IL or missing references) //IL_0168: Unknown result type (might be due to invalid IL or missing references) //IL_0143: Unknown result type (might be due to invalid IL or missing references) //IL_0160: Unknown result type (might be due to invalid IL or missing references) //IL_0158: 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_0133: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) Rect(72f, 13f, 16f, 25f, 4f, Soft); Ellipse(80f, 14f, 27f, 5f, Soft); Circle(80f, 58f, 36f, Gold); Circle(80f, 58f, 33.5f, Ink); int num = game switch { 9 => 12, 11 => 24, _ => 16, }; for (int i = 50; i < 186; i++) { for (int j = 90; j < 232; j++) { float num2 = ((float)j + 0.5f) / 2f - 80f; float num3 = ((float)i + 0.5f) / 2f - 58f; float num4 = Mathf.Sqrt(num2 * num2 + num3 * num3); if (!(num4 > 32f)) { int num5 = (int)((Mathf.Atan2(num3, num2) + (float)Math.PI) / ((float)Math.PI * 2f) * (float)num); Paint(j, i, (Color)(game switch { 9 => (num5 % 3 == 0) ? Gold : ((num5 % 3 == 1) ? Mint : Soft), 11 => (num5 == 0) ? Mint : ((num5 % 2 == 0) ? Red : Ink), _ => (num5 % 4 == 0) ? Gold : ((num5 % 4 == 1) ? Red : ((num5 % 4 == 2) ? Shade(110, 142, 218) : Mint)), }), 1f); } } } for (int k = 0; k < num; k++) { float num6 = (float)(k * 2) * (float)Math.PI / (float)num; Line(80f + Mathf.Cos(num6) * 10f, 58f + Mathf.Sin(num6) * 10f, 80f + Mathf.Cos(num6) * 32f, 58f + Mathf.Sin(num6) * 32f, 0.6f, Ink); Circle(80f + Mathf.Cos(num6 + (float)Math.PI / (float)num) * 27f, 58f + Mathf.Sin(num6 + (float)Math.PI / (float)num) * 27f, 1f, Paper); } Circle(80f, 58f, 10f, Gold); Circle(80f, 58f, 7f, Ink); Circle(80f, 58f, 3f, Gold); Circle(80f, 95f, 3f, Paper); if (game == 9) { Chip(120f, 28f, 11f, Gold); Chip(133f, 22f, 8f, Mint); } if (game == 11) { Circle(110f, 76f, 3f, Paper); Chip(36f, 24f, 9f, Red); } if (game == 13) { Circle(35f, 83f, 2f, Gold); Circle(127f, 76f, 2f, Gold); Circle(132f, 88f, 1f, Paper); } } public void Draw(int game) { //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_00e2: Unknown result type (might be due to invalid IL or missing references) //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0142: Unknown result type (might be due to invalid IL or missing references) //IL_0189: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_02fe: Unknown result type (might be due to invalid IL or missing references) //IL_0323: Unknown result type (might be due to invalid IL or missing references) //IL_050e: Unknown result type (might be due to invalid IL or missing references) //IL_052a: Unknown result type (might be due to invalid IL or missing references) //IL_0545: Unknown result type (might be due to invalid IL or missing references) //IL_06f9: Unknown result type (might be due to invalid IL or missing references) //IL_0b5a: Unknown result type (might be due to invalid IL or missing references) //IL_0b7f: Unknown result type (might be due to invalid IL or missing references) //IL_0c99: Unknown result type (might be due to invalid IL or missing references) //IL_0d43: Unknown result type (might be due to invalid IL or missing references) //IL_0d63: Unknown result type (might be due to invalid IL or missing references) //IL_0d8e: Unknown result type (might be due to invalid IL or missing references) //IL_0dae: Unknown result type (might be due to invalid IL or missing references) //IL_0dd3: Unknown result type (might be due to invalid IL or missing references) //IL_0df8: Unknown result type (might be due to invalid IL or missing references) //IL_0e1d: Unknown result type (might be due to invalid IL or missing references) //IL_0e4b: Unknown result type (might be due to invalid IL or missing references) //IL_0e6b: Unknown result type (might be due to invalid IL or missing references) //IL_0e99: Unknown result type (might be due to invalid IL or missing references) //IL_0eb4: Unknown result type (might be due to invalid IL or missing references) //IL_0eda: Unknown result type (might be due to invalid IL or missing references) //IL_0eff: Unknown result type (might be due to invalid IL or missing references) //IL_0f8a: Unknown result type (might be due to invalid IL or missing references) //IL_0fa5: Unknown result type (might be due to invalid IL or missing references) //IL_01fd: Unknown result type (might be due to invalid IL or missing references) //IL_0351: Unknown result type (might be due to invalid IL or missing references) //IL_0381: Unknown result type (might be due to invalid IL or missing references) //IL_03b4: Unknown result type (might be due to invalid IL or missing references) //IL_03d9: Unknown result type (might be due to invalid IL or missing references) //IL_03f9: Unknown result type (might be due to invalid IL or missing references) //IL_0414: Unknown result type (might be due to invalid IL or missing references) //IL_0439: Unknown result type (might be due to invalid IL or missing references) //IL_0454: Unknown result type (might be due to invalid IL or missing references) //IL_046f: Unknown result type (might be due to invalid IL or missing references) //IL_0494: Unknown result type (might be due to invalid IL or missing references) //IL_04b9: Unknown result type (might be due to invalid IL or missing references) //IL_05df: Unknown result type (might be due to invalid IL or missing references) //IL_05fa: Unknown result type (might be due to invalid IL or missing references) //IL_061f: Unknown result type (might be due to invalid IL or missing references) //IL_0649: Unknown result type (might be due to invalid IL or missing references) //IL_0673: Unknown result type (might be due to invalid IL or missing references) //IL_068e: Unknown result type (might be due to invalid IL or missing references) //IL_06b3: Unknown result type (might be due to invalid IL or missing references) //IL_06ce: Unknown result type (might be due to invalid IL or missing references) //IL_0736: Unknown result type (might be due to invalid IL or missing references) //IL_075c: Unknown result type (might be due to invalid IL or missing references) //IL_0777: Unknown result type (might be due to invalid IL or missing references) //IL_0792: Unknown result type (might be due to invalid IL or missing references) //IL_0941: Unknown result type (might be due to invalid IL or missing references) //IL_0bad: Unknown result type (might be due to invalid IL or missing references) //IL_0bcd: Unknown result type (might be due to invalid IL or missing references) //IL_0bfd: Unknown result type (might be due to invalid IL or missing references) //IL_0c22: Unknown result type (might be due to invalid IL or missing references) //IL_0c3d: Unknown result type (might be due to invalid IL or missing references) //IL_0c58: Unknown result type (might be due to invalid IL or missing references) //IL_0c73: Unknown result type (might be due to invalid IL or missing references) //IL_0cff: Unknown result type (might be due to invalid IL or missing references) //IL_0d1a: Unknown result type (might be due to invalid IL or missing references) //IL_0234: Unknown result type (might be due to invalid IL or missing references) //IL_0998: Unknown result type (might be due to invalid IL or missing references) //IL_09be: Unknown result type (might be due to invalid IL or missing references) //IL_0a1d: Unknown result type (might be due to invalid IL or missing references) //IL_0ab5: Unknown result type (might be due to invalid IL or missing references) //IL_0ada: Unknown result type (might be due to invalid IL or missing references) //IL_0aff: Unknown result type (might be due to invalid IL or missing references) //IL_0b1a: Unknown result type (might be due to invalid IL or missing references) //IL_05a9: Unknown result type (might be due to invalid IL or missing references) //IL_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_080a: Unknown result type (might be due to invalid IL or missing references) //IL_0802: Unknown result type (might be due to invalid IL or missing references) //IL_0910: Unknown result type (might be due to invalid IL or missing references) //IL_0908: Unknown result type (might be due to invalid IL or missing references) //IL_0a85: Unknown result type (might be due to invalid IL or missing references) //IL_0289: Unknown result type (might be due to invalid IL or missing references) //IL_02ae: Unknown result type (might be due to invalid IL or missing references) //IL_02d3: Unknown result type (might be due to invalid IL or missing references) //IL_0821: Unknown result type (might be due to invalid IL or missing references) //IL_0819: Unknown result type (might be due to invalid IL or missing references) //IL_0a7d: Unknown result type (might be due to invalid IL or missing references) //IL_0826: Unknown result type (might be due to invalid IL or missing references) //IL_0a75: Unknown result type (might be due to invalid IL or missing references) //IL_088c: Unknown result type (might be due to invalid IL or missing references) //IL_0849: Unknown result type (might be due to invalid IL or missing references) //IL_086b: Unknown result type (might be due to invalid IL or missing references) switch (game) { case 0: Ellipse(80f, 41f, 60f, 27f, Shade(39, 99, 80)); Ellipse(80f, 41f, 57f, 24f, Shade(26, 70, 62)); Card(45f, 31f, 32f, 48f, heart: false, 1); Card(81f, 35f, 32f, 48f, heart: true, 6); Chip(125f, 24f, 10f, Gold); Chip(26f, 29f, 8f, Red); break; case 1: Panel(32f, 23f, 96f, 57f); Rect(37f, 28f, 86f, 47f, 5f, Shade(29, 86, 69)); Dice(49f, 35f, 28f, 6); Dice(83f, 46f, 26f, 1); Chip(126f, 27f, 9f, Gold); break; case 2: { Panel(24f, 22f, 112f, 64f); Rect(32f, 30f, 96f, 48f, 3f, Shade(13, 35, 47)); for (int m = 0; m < 4; m++) { Line(34f, 34 + m * 12, 124f, 34 + m * 12, 0.5f, Soft); } for (int n = 0; n < 6; n++) { Line(36 + n * 17, 32f, 36 + n * 17, 76f, 0.5f, Soft); } float num2 = 37f; float num3 = 35f; for (int num4 = 1; num4 <= 24; num4++) { float num5 = 37f + (float)num4 * 3.4f; float num6 = 35f + (float)(num4 * num4) * 0.064f; Line(num2, num3, num5, num6, 2.5f, Mint); num2 = num5; num3 = num6; } Circle(num2, num3, 4f, Gold); Line(71f, 21f, 89f, 21f, 3f, Soft); break; } case 3: { Rect(22f, 21f, 116f, 64f, 5f, Shade(71, 102, 79)); Rect(22f, 34f, 116f, 37f, 0f, Ink); for (int num16 = 0; num16 < 6; num16++) { Rect(27 + num16 * 19, 51f, 9f, 1.6f, 0f, Paper); } Rect(31f, 59f, 27f, 9f, 2f, Red); Rect(35f, 60f, 8f, 7f, 1f, Shade(132, 189, 204)); Rect(104f, 37f, 27f, 9f, 2f, Gold); Ellipse(83f, 53f, 9f, 10f, Paper); Circle(88f, 63f, 6f, Paper); Rect(92f, 61f, 5f, 3f, 0.5f, Gold); Circle(89f, 65f, 1f, Ink); Circle(87f, 69f, 2f, Red); Line(79f, 43f, 77f, 38f, 2f, Gold); Line(86f, 43f, 88f, 38f, 2f, Gold); break; } case 4: Card(40f, 27f, 34f, 53f, heart: true, 2); Card(86f, 27f, 34f, 53f, heart: false, 8); Digit(2, 48f, 29f, 0.65f, Red); Digit(8, 94f, 29f, 0.65f, Ink); Chip(80f, 18f, 8f, Gold); break; case 5: { Panel(41f, 17f, 63f, 72f); for (int j = 0; j < 6; j++) { for (int k = 0; k < 3; k++) { Rect(48 + k * 17, 23 + j * 10, 13f, 7f, 1.4f, (k == j * 2 % 3) ? Mint : Soft); } } Ellipse(120f, 66f, 12f, 8f, Gold); Circle(125f, 75f, 8f, Gold); Rect(127f, 70f, 14f, 7f, 2f, Gold); Triangle(116f, 80f, 112f, 91f, 121f, 82f, Gold); Triangle(124f, 82f, 128f, 91f, 130f, 80f, Gold); Circle(128f, 78f, 1.5f, Ink); Line(111f, 63f, 103f, 58f, 5f, Gold); Circle(26f, 35f, 4f, Mint); break; } case 6: { Rect(17f, 25f, 126f, 50f, 6f, Shade(32, 99, 115)); for (int num15 = 0; num15 < 3; num15++) { Line(21f, 34 + num15 * 16, 139f, 34 + num15 * 16, 0.8f, Shade(117, 175, 184)); } Duck(46f, 37f, 0.65f, Gold); Duck(91f, 56f, 0.8f, Paper); Duck(119f, 29f, 0.55f, Red); break; } case 7: { Panel(28f, 19f, 104f, 67f); for (int num10 = 0; num10 < 4; num10++) { for (int num11 = 0; num11 < 6; num11++) { float num12 = 39 + num11 * 16; float num13 = 30 + num10 * 14; int num14 = num10 * 6 + num11 + 1; bool flag = (num14 - 1) % 5 == 0; Circle(num12, num13, 6f, flag ? Gold : Soft); Color color2 = (flag ? Ink : Paper); if (num14 >= 10) { Digit(num14 / 10, num12 - 3.7f, num13 - 2.4f, 0.55f, color2); Digit(num14 % 10, num12 + 0.7f, num13 - 2.4f, 0.55f, color2); } else { Digit(num14, num12 - 1.5f, num13 - 3f, 0.7f, color2); } } } break; } case 8: { Panel(35f, 18f, 90f, 69f); for (int num17 = 0; num17 < 4; num17++) { for (int num18 = 0; num18 < 5; num18++) { float x3 = 43 + num18 * 15; float y = 25 + num17 * 14; Rect(x3, y, 12f, 11f, 1.5f, ((num18 + num17) % 3 == 0) ? Mint : Soft); } } Circle(93f, 44f, 5f, Ink); for (int num19 = 0; num19 < 8; num19++) { float num20 = (float)num19 * (float)Math.PI / 4f; Line(93f, 44f, 93f + Mathf.Cos(num20) * 8f, 44f + Mathf.Sin(num20) * 8f, 1.5f, Ink); } Circle(91f, 46f, 1.2f, Paper); break; } case 9: case 11: case 13: Wheel(game); break; case 10: { Panel(32f, 17f, 96f, 74f); for (int num7 = 0; num7 < 6; num7++) { for (int num8 = 0; num8 <= num7; num8++) { Circle(80f + ((float)num8 - (float)num7 / 2f) * 12f, 78 - num7 * 8, 1.9f, Paper); } } for (int num9 = 0; num9 < 7; num9++) { float x2 = 39 + num9 * 12; Color color; switch (num9) { default: color = Mint; break; case 1: case 5: color = Gold; break; case 0: case 6: color = Red; break; } Rect(x2, 23f, 10f, 8f, 1f, color); } Line(80f, 83f, 85f, 71f, 1f, Gold); Line(85f, 71f, 73f, 61f, 1f, Gold); Line(73f, 61f, 79f, 50f, 1f, Gold); Circle(79f, 50f, 4f, Gold); break; } case 12: { Panel(41f, 17f, 78f, 73f); Rect(45f, 73f, 70f, 12f, 3f, Gold); Rect(47f, 36f, 66f, 30f, 3f, Soft); for (int l = 0; l < 3; l++) { Rect(49 + l * 21, 38f, 19f, 26f, 2f, Paper); Digit(7, 54 + l * 21, 44f, 1.6f, Red); } Rect(50f, 23f, 57f, 8f, 2f, Gold); Rect(119f, 40f, 4f, 21f, 2f, Soft); Circle(122f, 63f, 5f, Red); Circle(56f, 79f, 1.5f, Paper); Circle(104f, 79f, 1.5f, Paper); break; } case 14: { Ellipse(80f, 35f, 64f, 22f, Shade(32, 90, 75)); for (int i = 0; i < 5; i++) { float x = 29 + i * 21; int num; switch (i) { default: num = 0; break; case 1: case 3: num = 3; break; case 2: num = 5; break; } Card(x, 34 + num, 24f, 39f, i % 2 == 0, 1); } Chip(41f, 20f, 9f, Red); Chip(113f, 20f, 10f, Gold); break; } case 15: Ellipse(59f, 43f, 25f, 29f, Shade(144, 101, 47)); Ellipse(57f, 47f, 25f, 29f, Gold); Ellipse(57f, 47f, 20f, 24f, Shade(181, 131, 64)); Ellipse(57f, 47f, 18f, 22f, Gold); Line(57f, 34f, 57f, 60f, 3f, Paper); Line(49f, 43f, 64f, 43f, 3f, Paper); Line(50f, 53f, 64f, 53f, 3f, Paper); Ellipse(105f, 60f, 16f, 22f, Shade(133, 168, 179)); Ellipse(103f, 63f, 16f, 22f, Paper); Ellipse(103f, 63f, 12f, 17f, Shade(133, 168, 179)); Circle(103f, 63f, 6f, Paper); break; case 16: Ellipse(80f, 34f, 64f, 23f, Shade(34, 81, 72)); Line(80f, 22f, 80f, 77f, 0.8f, Gold); Card(32f, 34f, 27f, 42f, heart: true, 3); Card(54f, 30f, 27f, 42f, heart: false, 1); Card(85f, 34f, 27f, 42f, heart: false, 2); Card(107f, 30f, 27f, 42f, heart: true, 1); Chip(42f, 20f, 8f, Red); Chip(119f, 20f, 8f, Gold); break; } } } private const int Width = 320; private const int Height = 200; private const int PreviewLayer = 31; private const int MaximumRenderers = 160; private const int MaximumVertices = 300000; private static readonly string[] PrefabPaths = new string[17] { "blackjack casinogame", "craps casinogame", "crash casinogame", "crossyroad casinogame", "hilo casinogame", null, "duckrace casinogame", null, "minesweeper casinogame", "moneywheel casinogame", "plinko casinogame", "roulette2 casinogame", "slotmachine casinogame", "wheeloffortune casinogame", "poker casinogame", "coinflip casinogame", "baccarat casinogame" }; private readonly int threadId = Thread.CurrentThread.ManagedThreadId; private readonly Action log; private readonly Dictionary cache = new Dictionary(); private readonly Dictionary sources = new Dictionary(); private readonly Queue pending = new Queue(); private readonly HashSet queued = new HashSet(); private readonly List textures = new List(); private readonly List ownedSprites = new List(); private readonly HashSet errors = new HashSet(); private GameObject cameraRoot; private Camera camera; private Light light; private Light fillLight; private RenderTexture target; private int lastFrame = -1; private bool disposed; public event Action ArtworkChanged; public ChaosStatsArt(Action logger = null) { log = logger; } public Sprite Get(int game) { CheckThread(); if (disposed || game < 0 || game >= PrefabPaths.Length) { return null; } if (cache.TryGetValue(game, out var value)) { return value; } if (queued.Add(game)) { pending.Enqueue(game); } return null; } public string Source(int game) { if (!sources.TryGetValue(game, out var value)) { return "Game picture loading"; } return value; } public void Tick(bool renderAllowed) { CheckThread(); if (disposed || !renderAllowed || pending.Count == 0 || lastFrame == Time.frameCount) { return; } lastFrame = Time.frameCount; int num = pending.Dequeue(); queued.Remove(num); Sprite val = null; string value = "Game illustration"; GameObject val2 = null; try { val2 = Prefab(num); if ((Object)(object)val2 != (Object)null) { val = Render(val2, num); } if ((Object)(object)val != (Object)null) { value = "Original game model"; } } catch (Exception error) { Report("original model preview", error); } if ((Object)(object)val == (Object)null) { val = Illustrate(num); } cache[num] = val; sources[num] = value; Action artworkChanged = this.ArtworkChanged; if (artworkChanged == null) { return; } try { artworkChanged(); } catch (Exception error2) { Report("stats artwork refresh", error2); } } private static GameObject Prefab(int game) { string text = PrefabPaths[game]; if (text != null) { GameObject val = Resources.Load(text); if ((Object)(object)val != (Object)null && (game == 15 || Matches(val, game))) { return val; } } for (int i = 1; i <= 4; i++) { Object val2 = Resources.Load("floorloottables/floor " + i); if (val2 == (Object)null || ((object)val2).GetType().FullName != "MoreMountains.Tools.MMLootTableGameObjectSO") { continue; } FieldInfo fieldInfo = Field(((object)val2).GetType(), "LootTable"); object obj = ((fieldInfo == null) ? null : fieldInfo.GetValue(val2)); FieldInfo fieldInfo2 = ((obj == null) ? null : Field(obj.GetType(), "ObjectsToLoot")); IEnumerable enumerable = ((fieldInfo2 == null) ? null : (fieldInfo2.GetValue(obj) as IEnumerable)); if (enumerable == null) { continue; } int num = 0; foreach (object item in enumerable) { if (++num <= 64) { FieldInfo fieldInfo3 = ((item == null) ? null : Field(item.GetType(), "Loot")); GameObject val3 = (GameObject)((fieldInfo3 == null) ? null : /*isinst with value type is only supported in some contexts*/); if ((Object)(object)val3 != (Object)null && Matches(val3, game)) { return val3; } continue; } break; } } return null; } private static bool Matches(GameObject prefab, int game) { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0017: Invalid comparison between Unknown and I4 GameBase component = prefab.GetComponent(); if ((Object)(object)component != (Object)null) { return (int)component.GameType == game; } return false; } private static FieldInfo Field(Type type, string name) { while (type != null) { FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (field != null) { return field; } type = type.BaseType; } return null; } private void EnsureCamera() { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Unknown result type (might be due to invalid IL or missing references) //IL_00de: Unknown result type (might be due to invalid IL or missing references) //IL_00e8: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01ec: Unknown result type (might be due to invalid IL or missing references) //IL_0271: Unknown result type (might be due to invalid IL or missing references) //IL_029c: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)camera != (Object)null)) { cameraRoot = VisualObject("Chaos original-art camera"); cameraRoot.transform.position = new Vector3(10000f, -10000f, 10000f); camera = cameraRoot.AddComponent(); ((Behaviour)camera).enabled = false; camera.clearFlags = (CameraClearFlags)2; camera.backgroundColor = new Color(0.075f, 0.105f, 0.15f, 1f); camera.cullingMask = int.MinValue; camera.orthographic = true; camera.allowHDR = false; camera.allowMSAA = false; camera.useOcclusionCulling = false; target = new RenderTexture(320, 200, 16, (RenderTextureFormat)0); ((Object)target).name = "Chaos cached original game artwork"; ((Object)target).hideFlags = (HideFlags)61; target.antiAliasing = 1; target.useMipMap = false; if (!target.Create()) { throw new InvalidOperationException("Small preview target could not be created."); } camera.targetTexture = target; GameObject val = VisualObject("Chaos original-art light"); val.transform.SetParent(cameraRoot.transform, false); light = val.AddComponent(); light.type = (LightType)1; light.cullingMask = int.MinValue; light.shadows = (LightShadows)0; light.intensity = 1.5f; light.color = new Color(1f, 0.94f, 0.84f); ((Behaviour)light).enabled = false; val.transform.rotation = Quaternion.Euler(45f, -35f, 0f); GameObject val2 = VisualObject("Chaos original-art fill light"); val2.transform.SetParent(cameraRoot.transform, false); fillLight = val2.AddComponent(); fillLight.type = (LightType)1; fillLight.cullingMask = int.MinValue; fillLight.shadows = (LightShadows)0; fillLight.intensity = 0.8f; fillLight.color = new Color(0.67f, 0.83f, 1f); ((Behaviour)fillLight).enabled = false; val2.transform.rotation = Quaternion.Euler(25f, 145f, 0f); } } private Sprite Render(GameObject prefab, int game) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Expected O, but got Unknown //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0417: Unknown result type (might be due to invalid IL or missing references) //IL_041c: Unknown result type (might be due to invalid IL or missing references) //IL_0293: Unknown result type (might be due to invalid IL or missing references) //IL_042a: Unknown result type (might be due to invalid IL or missing references) //IL_0444: Unknown result type (might be due to invalid IL or missing references) //IL_0450: Unknown result type (might be due to invalid IL or missing references) //IL_045c: Unknown result type (might be due to invalid IL or missing references) //IL_0357: Unknown result type (might be due to invalid IL or missing references) //IL_035e: Expected O, but got Unknown //IL_04ea: Unknown result type (might be due to invalid IL or missing references) //IL_04d4: Unknown result type (might be due to invalid IL or missing references) //IL_04ef: Unknown result type (might be due to invalid IL or missing references) //IL_04f3: Unknown result type (might be due to invalid IL or missing references) //IL_04f8: Unknown result type (might be due to invalid IL or missing references) //IL_0507: Unknown result type (might be due to invalid IL or missing references) //IL_050c: Unknown result type (might be due to invalid IL or missing references) //IL_051c: Unknown result type (might be due to invalid IL or missing references) //IL_0521: Unknown result type (might be due to invalid IL or missing references) //IL_0536: Unknown result type (might be due to invalid IL or missing references) //IL_0538: Unknown result type (might be due to invalid IL or missing references) //IL_053d: Unknown result type (might be due to invalid IL or missing references) //IL_056f: Unknown result type (might be due to invalid IL or missing references) //IL_0576: Unknown result type (might be due to invalid IL or missing references) //IL_0693: Unknown result type (might be due to invalid IL or missing references) //IL_069a: Expected O, but got Unknown //IL_06d0: Unknown result type (might be due to invalid IL or missing references) //IL_071d: Unknown result type (might be due to invalid IL or missing references) //IL_072c: Unknown result type (might be due to invalid IL or missing references) //IL_059c: Unknown result type (might be due to invalid IL or missing references) //IL_05a1: Unknown result type (might be due to invalid IL or missing references) //IL_05a6: Unknown result type (might be due to invalid IL or missing references) //IL_05ab: Unknown result type (might be due to invalid IL or missing references) //IL_05b8: Unknown result type (might be due to invalid IL or missing references) //IL_05ba: Unknown result type (might be due to invalid IL or missing references) //IL_05bf: Unknown result type (might be due to invalid IL or missing references) EnsureCamera(); StandardRequest val = new StandardRequest(); val.destination = target; StandardRequest val2 = val; bool flag = RenderPipelineManager.currentPipeline != null; if (flag && !RenderPipeline.SupportsRenderRequest(camera, val2)) { return null; } GameObject val3 = VisualObject("Chaos original model only"); val3.SetActive(false); val3.transform.position = new Vector3(10000f, -10000f, 10000f); val3.transform.localScale = prefab.transform.localScale; val3.transform.rotation = prefab.transform.localRotation; Dictionary dictionary = new Dictionary(); dictionary[prefab.transform] = val3.transform; List list = new List(); Texture2D val4 = null; Sprite val5 = null; RenderTexture active = RenderTexture.active; try { int num = 0; int num2 = 0; MeshRenderer[] componentsInChildren = prefab.GetComponentsInChildren(true); foreach (MeshRenderer val6 in componentsInChildren) { MeshFilter component = ((Component)val6).GetComponent(); if (((Renderer)val6).enabled && VisibleInPrefab(((Component)val6).transform, prefab.transform) && !((Object)(object)component == (Object)null) && !((Object)(object)component.sharedMesh == (Object)null)) { if (++num > 160 || (num2 += component.sharedMesh.vertexCount) > 300000) { throw new InvalidOperationException("Original preview mesh budget exceeded."); } Transform val7 = Node(((Component)val6).transform, prefab.transform, val3.transform, dictionary); MeshFilter val8 = ((Component)val7).gameObject.AddComponent(); val8.sharedMesh = component.sharedMesh; MeshRenderer val9 = ((Component)val7).gameObject.AddComponent(); ((Renderer)val9).sharedMaterials = ((Renderer)val6).sharedMaterials; ((Renderer)val9).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val9).receiveShadows = false; ((Renderer)val9).lightProbeUsage = (LightProbeUsage)0; ((Renderer)val9).reflectionProbeUsage = (ReflectionProbeUsage)0; } } SpriteRenderer[] componentsInChildren2 = prefab.GetComponentsInChildren(true); foreach (SpriteRenderer val10 in componentsInChildren2) { if (((Renderer)val10).enabled && !((Object)(object)val10.sprite == (Object)null) && VisibleInPrefab(((Component)val10).transform, prefab.transform)) { if (++num > 160) { throw new InvalidOperationException("Original preview renderer budget exceeded."); } Transform val11 = Node(((Component)val10).transform, prefab.transform, val3.transform, dictionary); SpriteRenderer val12 = ((Component)val11).gameObject.AddComponent(); val12.sprite = val10.sprite; ((Renderer)val12).sharedMaterial = ((Renderer)val10).sharedMaterial; val12.color = val10.color; val12.flipX = val10.flipX; val12.flipY = val10.flipY; ((Renderer)val12).sortingOrder = ((Renderer)val10).sortingOrder; } } SkinnedMeshRenderer[] componentsInChildren3 = prefab.GetComponentsInChildren(true); foreach (SkinnedMeshRenderer val13 in componentsInChildren3) { if (((Renderer)val13).enabled && !((Object)(object)val13.sharedMesh == (Object)null) && VisibleInPrefab(((Component)val13).transform, prefab.transform)) { if (++num > 160 || (num2 += val13.sharedMesh.vertexCount) > 300000) { throw new InvalidOperationException("Original preview mesh budget exceeded."); } Mesh val14 = new Mesh(); ((Object)val14).hideFlags = (HideFlags)61; list.Add(val14); val13.BakeMesh(val14); Transform val15 = Node(((Component)val13).transform, prefab.transform, val3.transform, dictionary); MeshFilter val16 = ((Component)val15).gameObject.AddComponent(); val16.sharedMesh = val14; MeshRenderer val17 = ((Component)val15).gameObject.AddComponent(); ((Renderer)val17).sharedMaterials = ((Renderer)val13).sharedMaterials; ((Renderer)val17).shadowCastingMode = (ShadowCastingMode)0; ((Renderer)val17).receiveShadows = false; ((Renderer)val17).lightProbeUsage = (LightProbeUsage)0; ((Renderer)val17).reflectionProbeUsage = (ReflectionProbeUsage)0; } } if (num == 0) { return null; } val3.SetActive(true); Renderer[] componentsInChildren4 = val3.GetComponentsInChildren(); Bounds bounds = componentsInChildren4[0].bounds; for (int l = 1; l < componentsInChildren4.Length; l++) { ((Bounds)(ref bounds)).Encapsulate(componentsInChildren4[l].bounds); } float num3 = Mathf.Max(((Bounds)(ref bounds)).size.x, Mathf.Max(((Bounds)(ref bounds)).size.y, ((Bounds)(ref bounds)).size.z)); if (float.IsNaN(num3) || float.IsInfinity(num3) || num3 < 0.001f || num3 > 100f) { return null; } Vector3 val18 = ((game == 0 || game == 1 || game == 4 || game == 8 || game == 11 || game == 14 || game == 16) ? new Vector3(-0.45f, -0.9f, -1.15f) : new Vector3(-0.5f, -0.35f, -1.4f)); Vector3 normalized = ((Vector3)(ref val18)).normalized; ((Component)camera).transform.position = ((Bounds)(ref bounds)).center - normalized * (num3 * 3f + 1f); ((Component)camera).transform.rotation = Quaternion.LookRotation(normalized, Vector3.up); camera.aspect = 1.6f; float num4 = 0f; float num5 = 0f; for (int m = 0; m < 8; m++) { Vector3 val19 = ((Bounds)(ref bounds)).center + Vector3.Scale(((Bounds)(ref bounds)).extents, new Vector3((float)(((m & 1) != 0) ? 1 : (-1)), (float)(((m & 2) != 0) ? 1 : (-1)), (float)(((m & 4) != 0) ? 1 : (-1)))); Vector3 val20 = ((Component)camera).transform.InverseTransformPoint(val19); num4 = Mathf.Max(num4, Mathf.Abs(val20.x)); num5 = Mathf.Max(num5, Mathf.Abs(val20.y)); } camera.orthographicSize = Mathf.Max(num5, num4 / camera.aspect) * 1.1f; camera.nearClipPlane = 0.03f; camera.farClipPlane = num3 * 8f + 4f; ((Behaviour)light).enabled = true; ((Behaviour)fillLight).enabled = true; if (flag) { RenderPipeline.SubmitRenderRequest(camera, val2); } else { camera.Render(); } RenderTexture.active = target; val4 = new Texture2D(320, 200, (TextureFormat)4, false); ((Object)val4).name = "Chaos original model: " + ((Object)prefab).name; ((Object)val4).hideFlags = (HideFlags)61; val4.ReadPixels(new Rect(0f, 0f, 320f, 200f), 0, 0, false); val4.Apply(false, false); ((Texture)val4).filterMode = (FilterMode)1; ((Texture)val4).wrapMode = (TextureWrapMode)1; if (!HasVisibleModel(val4)) { return null; } val5 = Sprite.Create(val4, new Rect(0f, 0f, 320f, 200f), new Vector2(0.5f, 0.5f), 100f); ((Object)val5).hideFlags = (HideFlags)61; textures.Add(val4); ownedSprites.Add(val5); val4 = null; return val5; } finally { RenderTexture.active = active; if ((Object)(object)light != (Object)null) { ((Behaviour)light).enabled = false; } if ((Object)(object)fillLight != (Object)null) { ((Behaviour)fillLight).enabled = false; } val3.SetActive(false); Object.Destroy((Object)(object)val3); foreach (Mesh item in list) { if ((Object)(object)item != (Object)null) { Object.Destroy((Object)(object)item); } } if ((Object)(object)val4 != (Object)null) { Object.Destroy((Object)(object)val4); } } } private static GameObject VisualObject(string name) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown GameObject val = new GameObject(name); ((Object)val).hideFlags = (HideFlags)61; val.layer = 31; return val; } private static Transform Node(Transform original, Transform root, Transform visual, Dictionary nodes) { //IL_0064: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_007c: Unknown result type (might be due to invalid IL or missing references) if (nodes.TryGetValue(original, out var value)) { return value; } if ((Object)(object)original == (Object)null || (Object)(object)original == (Object)(object)root || (Object)(object)original.parent == (Object)null) { throw new InvalidOperationException("Original model hierarchy is incomplete."); } Transform val = Node(original.parent, root, visual, nodes); GameObject val2 = VisualObject(((Object)original).name); value = val2.transform; value.SetParent(val, false); value.localPosition = original.localPosition; value.localRotation = original.localRotation; value.localScale = original.localScale; nodes.Add(original, value); return value; } private static bool VisibleInPrefab(Transform item, Transform root) { int num = 0; while ((Object)(object)item != (Object)null && num < 64) { if ((Object)(object)item == (Object)(object)root) { return true; } if (!((Component)item).gameObject.activeSelf) { return false; } num++; item = item.parent; } return false; } private static bool HasVisibleModel(Texture2D image) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0028: 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) Color32[] pixels = image.GetPixels32(); Color32 val = pixels[0]; int num = 0; int num2 = 0; for (int i = 0; i < pixels.Length; i += 9) { Color32 val2 = pixels[i]; int num3 = Math.Abs(val2.r - val.r) + Math.Abs(val2.g - val.g) + Math.Abs(val2.b - val.b); if (num3 > 42) { num++; } if (val2.r > 210 && val2.b > 210 && val2.g < 70) { num2++; } } if (num >= 80) { return num2 < Math.Max(24, num / 3); } return false; } private Sprite Illustrate(int game) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Expected O, but got Unknown //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_0084: Unknown result type (might be due to invalid IL or missing references) Illustration illustration = new Illustration(game); illustration.Draw(game); Texture2D val = new Texture2D(320, 200, (TextureFormat)4, false); ((Object)val).name = "Chaos game illustration: " + game; ((Object)val).hideFlags = (HideFlags)61; ((Texture)val).filterMode = (FilterMode)1; ((Texture)val).wrapMode = (TextureWrapMode)1; try { val.SetPixels32(illustration.Pixels); val.Apply(false, true); Sprite val2 = Sprite.Create(val, new Rect(0f, 0f, 320f, 200f), new Vector2(0.5f, 0.5f), 100f); ((Object)val2).name = ((Object)val).name; ((Object)val2).hideFlags = (HideFlags)61; textures.Add(val); ownedSprites.Add(val2); return val2; } catch { Object.Destroy((Object)(object)val); throw; } } private void CheckThread() { if (Thread.CurrentThread.ManagedThreadId != threadId) { throw new InvalidOperationException("Stats artwork belongs to the main Unity thread."); } } private void Report(string operation, Exception error) { if (errors.Count < 4 && errors.Add(operation) && log != null) { try { log(operation + " unavailable: " + error.GetBaseException().Message); } catch { } } } public void Dispose() { CheckThread(); if (disposed) { return; } disposed = true; pending.Clear(); queued.Clear(); cache.Clear(); sources.Clear(); this.ArtworkChanged = null; foreach (Sprite ownedSprite in ownedSprites) { if ((Object)(object)ownedSprite != (Object)null) { Object.Destroy((Object)(object)ownedSprite); } } foreach (Texture2D texture in textures) { if ((Object)(object)texture != (Object)null) { Object.Destroy((Object)(object)texture); } } ownedSprites.Clear(); textures.Clear(); if ((Object)(object)camera != (Object)null) { camera.targetTexture = null; } if ((Object)(object)target != (Object)null) { target.Release(); Object.Destroy((Object)(object)target); } if ((Object)(object)cameraRoot != (Object)null) { Object.Destroy((Object)(object)cameraRoot); } target = null; camera = null; light = null; fillLight = null; cameraRoot = null; } } [Serializable] public struct StatsAmount { public const long MaximumExponent = 1000000064L; private const double PrecisionScale = 100000000000000.0; public double Mantissa; public long Exponent; public static readonly StatsAmount Zero = default(StatsAmount); public StatsAmount(double mantissa, long exponent) { if (double.IsNaN(mantissa) || double.IsInfinity(mantissa) || exponent < -1000000064 || exponent > 1000000064) { throw new ArgumentException("Invalid statistics cash amount."); } if (mantissa == 0.0) { Mantissa = 0.0; Exponent = 0L; return; } while (Math.Abs(mantissa) >= 10.0) { mantissa /= 10.0; exponent++; } while (Math.Abs(mantissa) < 1.0) { mantissa *= 10.0; exponent--; } mantissa = Math.Round(mantissa * 100000000000000.0) / 100000000000000.0; if (Math.Abs(mantissa) >= 10.0) { mantissa /= 10.0; exponent++; } if (exponent < -1000000064 || exponent > 1000000064) { throw new OverflowException("Statistics cash exponent is outside its bound."); } Mantissa = mantissa; Exponent = exponent; } public static bool IsValid(StatsAmount value) { if (!double.IsNaN(value.Mantissa) && !double.IsInfinity(value.Mantissa) && value.Exponent >= -1000000064 && value.Exponent <= 1000000064) { if (value.Mantissa != 0.0) { if (Math.Abs(value.Mantissa) >= 1.0) { return Math.Abs(value.Mantissa) < 10.0; } return false; } return value.Exponent == 0; } return false; } private static void Require(StatsAmount value) { if (!IsValid(value)) { throw new ArgumentException("Statistics cash is not normalized."); } } public static int Compare(StatsAmount left, StatsAmount right) { Require(left); Require(right); int num = Math.Sign(left.Mantissa); int num2 = Math.Sign(right.Mantissa); if (num != num2) { return num.CompareTo(num2); } if (num == 0) { return 0; } int num3 = ((left.Exponent != right.Exponent) ? left.Exponent.CompareTo(right.Exponent) : ((!(Math.Abs(Math.Abs(left.Mantissa) - Math.Abs(right.Mantissa)) < 1E-14)) ? Math.Abs(left.Mantissa).CompareTo(Math.Abs(right.Mantissa)) : 0)); if (num <= 0) { return -num3; } return num3; } public static StatsAmount Add(StatsAmount left, StatsAmount right) { Require(left); Require(right); if (left.Mantissa == 0.0) { return right; } if (right.Mantissa == 0.0) { return left; } if (left.Exponent < right.Exponent) { StatsAmount statsAmount = left; left = right; right = statsAmount; } long num = left.Exponent - right.Exponent; if (num >= 15) { return left; } return new StatsAmount(left.Mantissa + right.Mantissa * Math.Pow(10.0, -num), left.Exponent); } public static StatsAmount Subtract(StatsAmount left, StatsAmount right) { Require(right); right.Mantissa = 0.0 - right.Mantissa; return Add(left, right); } public static StatsAmount Multiply(StatsAmount value, double factor) { Require(value); StatsAmount statsAmount = new StatsAmount(factor, 0L); if (value.Mantissa == 0.0 || statsAmount.Mantissa == 0.0) { return Zero; } return new StatsAmount(value.Mantissa * statsAmount.Mantissa, value.Exponent + statsAmount.Exponent); } public static bool AtLeastMultiple(StatsAmount value, StatsAmount basis, double multiple) { Require(value); Require(basis); if (value.Mantissa < 0.0 || basis.Mantissa <= 0.0 || double.IsNaN(multiple) || double.IsInfinity(multiple) || multiple < 0.0) { throw new ArgumentException("A payout threshold needs nonnegative payout and positive stake."); } if (multiple == 0.0) { return true; } if (value.Mantissa == 0.0) { return false; } StatsAmount statsAmount = new StatsAmount(multiple, 0L); double num = basis.Mantissa * statsAmount.Mantissa; long num2 = basis.Exponent + statsAmount.Exponent; while (num >= 10.0) { num /= 10.0; num2++; } num = Math.Round(num * 100000000000000.0) / 100000000000000.0; if (num >= 10.0) { num /= 10.0; num2++; } if (value.Exponent != num2) { return value.Exponent > num2; } if (!(value.Mantissa >= num)) { return Math.Abs(value.Mantissa - num) < 1E-14; } return true; } } [Serializable] public sealed class StatsSettlement { public string ReceiptId; public string RunId; public string Stream; public string PlayerName; public long Sequence; public ulong Player; public int Game; public StatsAmount Bet; public StatsAmount Payout; } [Serializable] public sealed class StatsRow { public ulong Player; public int Game; public string PlayerName = ""; public long Plays; public long Wins; public long Losses; public long Ties; public long BigWins; public StatsAmount Wager; public StatsAmount Payout; public StatsAmount Net; public StatsAmount GrossProfit; public StatsAmount GrossLoss; public StatsAmount LargestNetWin; public StatsAmount LargestStake; public long WinStreak; public long LossStreak; public long BestWinStreak; public long BestLossStreak; } [Serializable] public sealed class StatsStreamState { public string Stream; public long Sequence; } [Serializable] public sealed class StatsState { public int Schema = 1; public ulong Owner; public string RunId = ""; public long Revision; public List Rows = new List(); public List Streams = new List(); public List Receipts = new List(); } public sealed class StatsLedger { public const int MaximumPlayers = 32; public const int NativeGameTypes = 17; public const int MaximumReceipts = 2048; public const int MaximumStreams = 128; public const int MaximumIdLength = 128; public const int MaximumNameLength = 64; public readonly StatsState State; private readonly Dictionary rows = new Dictionary(StringComparer.Ordinal); private readonly Dictionary streams = new Dictionary(StringComparer.Ordinal); private readonly HashSet receipts = new HashSet(StringComparer.Ordinal); private readonly HashSet players = new HashSet(); public StatsLedger(ulong owner, string runId) { State = new StatsState { Owner = owner, RunId = (runId ?? "") }; Validate(State); } public static bool ValidId(string value) { if (string.IsNullOrEmpty(value) || value.Length > 128) { return false; } foreach (char c in value) { if (c < '!' || c > '~') { return false; } } return true; } public static string CleanName(string value) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(Math.Min(value.Length, 64)); foreach (char c in value) { if (!char.IsControl(c)) { if (stringBuilder.Length == 64) { break; } stringBuilder.Append(c); } } if (stringBuilder.Length > 0 && char.IsHighSurrogate(stringBuilder[stringBuilder.Length - 1])) { stringBuilder.Length--; } return stringBuilder.ToString(); } private static string Key(ulong player, int game) { return player.ToString(CultureInfo.InvariantCulture) + "/" + game.ToString(CultureInfo.InvariantCulture); } public bool ApplyPaidSettlement(StatsSettlement settlement, out string reason) { reason = "Invalid paid settlement."; if (settlement == null || !ValidId(settlement.ReceiptId) || !ValidId(settlement.RunId) || !ValidId(settlement.Stream) || settlement.Sequence <= 0 || settlement.Player == 0 || settlement.Game < 0 || settlement.Game >= 17 || !StatsAmount.IsValid(settlement.Bet) || settlement.Bet.Mantissa <= 0.0 || !StatsAmount.IsValid(settlement.Payout) || settlement.Payout.Mantissa < 0.0) { return false; } if (State.Owner != 0 && State.Owner != settlement.Player) { reason = "Lifetime statistics belong to the local player only."; return false; } if (State.Owner == 0 && !string.Equals(State.RunId, settlement.RunId, StringComparison.Ordinal)) { reason = "Settlement belongs to another run."; return false; } if (receipts.Contains(settlement.ReceiptId) || (streams.TryGetValue(settlement.Stream, out var value) && settlement.Sequence <= value.Sequence)) { reason = "Settlement was already counted or is older than this stream."; return false; } if (!players.Contains(settlement.Player) && players.Count >= 32) { reason = "Current-run player bound reached."; return false; } if (State.Revision == long.MaxValue) { reason = "Statistics revision bound reached."; return false; } string key = Key(settlement.Player, settlement.Game); rows.TryGetValue(key, out var value2); StatsRow statsRow; try { statsRow = Advance(value2, settlement); } catch (OverflowException) { reason = "Statistics amount or counter bound reached."; return false; } catch (ArgumentException) { reason = "Statistics amount or counter bound reached."; return false; } if (value2 == null) { State.Rows.Add(statsRow); } else { State.Rows[State.Rows.IndexOf(value2)] = statsRow; } rows[key] = statsRow; players.Add(settlement.Player); if (!streams.TryGetValue(settlement.Stream, out value)) { if (State.Streams.Count == 128) { streams.Remove(State.Streams[0].Stream); State.Streams.RemoveAt(0); } StatsStreamState statsStreamState = new StatsStreamState(); statsStreamState.Stream = settlement.Stream; value = statsStreamState; State.Streams.Add(value); streams.Add(value.Stream, value); } else { State.Streams.Remove(value); State.Streams.Add(value); } value.Sequence = settlement.Sequence; if (State.Receipts.Count == 2048) { receipts.Remove(State.Receipts[0]); State.Receipts.RemoveAt(0); } State.Receipts.Add(settlement.ReceiptId); receipts.Add(settlement.ReceiptId); State.Revision++; reason = "Paid settlement counted."; return true; } private static StatsRow Advance(StatsRow existing, StatsSettlement settlement) { StatsRow statsRow; if (existing != null) { statsRow = Copy(existing); } else { StatsRow statsRow2 = new StatsRow(); statsRow2.Player = settlement.Player; statsRow2.Game = settlement.Game; statsRow = statsRow2; } StatsRow statsRow3 = statsRow; statsRow3.PlayerName = CleanName(settlement.PlayerName); StatsAmount right = StatsAmount.Subtract(settlement.Payout, settlement.Bet); checked { statsRow3.Plays++; if (right.Mantissa > 0.0) { statsRow3.Wins++; statsRow3.WinStreak++; statsRow3.LossStreak = 0L; statsRow3.BestWinStreak = Math.Max(statsRow3.BestWinStreak, statsRow3.WinStreak); } else if (right.Mantissa < 0.0) { statsRow3.Losses++; statsRow3.LossStreak++; statsRow3.WinStreak = 0L; statsRow3.BestLossStreak = Math.Max(statsRow3.BestLossStreak, statsRow3.LossStreak); } else { statsRow3.Ties++; statsRow3.WinStreak = 0L; statsRow3.LossStreak = 0L; } if (StatsAmount.AtLeastMultiple(settlement.Payout, settlement.Bet, 3.0)) { statsRow3.BigWins++; } statsRow3.Wager = StatsAmount.Add(statsRow3.Wager, settlement.Bet); statsRow3.Payout = StatsAmount.Add(statsRow3.Payout, settlement.Payout); if (right.Mantissa > 0.0) { statsRow3.GrossProfit = StatsAmount.Add(statsRow3.GrossProfit, right); } else if (right.Mantissa < 0.0) { right.Mantissa = 0.0 - right.Mantissa; statsRow3.GrossLoss = StatsAmount.Add(statsRow3.GrossLoss, right); } statsRow3.Net = StatsAmount.Subtract(statsRow3.GrossProfit, statsRow3.GrossLoss); StatsAmount statsAmount = StatsAmount.Subtract(settlement.Payout, settlement.Bet); if (StatsAmount.Compare(statsAmount, statsRow3.LargestNetWin) > 0) { statsRow3.LargestNetWin = statsAmount; } if (StatsAmount.Compare(settlement.Bet, statsRow3.LargestStake) > 0) { statsRow3.LargestStake = settlement.Bet; } return statsRow3; } } public StatsRow Summarize(ulong player, int game) { if (game < -1 || game >= 17) { throw new ArgumentException("Unknown native statistics game."); } StatsRow statsRow = new StatsRow(); statsRow.Player = player; statsRow.Game = game; StatsRow statsRow2 = statsRow; checked { foreach (StatsRow row in State.Rows) { if ((player == 0 || row.Player == player) && (game < 0 || row.Game == game)) { statsRow2.Plays += row.Plays; statsRow2.Wins += row.Wins; statsRow2.Losses += row.Losses; statsRow2.Ties += row.Ties; statsRow2.BigWins += row.BigWins; statsRow2.Wager = StatsAmount.Add(statsRow2.Wager, row.Wager); statsRow2.Payout = StatsAmount.Add(statsRow2.Payout, row.Payout); statsRow2.GrossProfit = StatsAmount.Add(statsRow2.GrossProfit, row.GrossProfit); statsRow2.GrossLoss = StatsAmount.Add(statsRow2.GrossLoss, row.GrossLoss); if (StatsAmount.Compare(row.LargestNetWin, statsRow2.LargestNetWin) > 0) { statsRow2.LargestNetWin = row.LargestNetWin; } if (StatsAmount.Compare(row.LargestStake, statsRow2.LargestStake) > 0) { statsRow2.LargestStake = row.LargestStake; } statsRow2.BestWinStreak = Math.Max(statsRow2.BestWinStreak, row.BestWinStreak); statsRow2.BestLossStreak = Math.Max(statsRow2.BestLossStreak, row.BestLossStreak); if (player != 0) { statsRow2.PlayerName = row.PlayerName; } if (player != 0 && game >= 0) { statsRow2.WinStreak = row.WinStreak; statsRow2.LossStreak = row.LossStreak; } } } statsRow2.Net = StatsAmount.Subtract(statsRow2.GrossProfit, statsRow2.GrossLoss); return statsRow2; } } public StatsState Snapshot() { StatsState statsState = new StatsState(); statsState.Schema = State.Schema; statsState.Owner = State.Owner; statsState.RunId = State.RunId; statsState.Revision = State.Revision; StatsState statsState2 = statsState; foreach (StatsRow row in State.Rows) { statsState2.Rows.Add(Copy(row)); } foreach (StatsStreamState stream in State.Streams) { statsState2.Streams.Add(new StatsStreamState { Stream = stream.Stream, Sequence = stream.Sequence }); } statsState2.Receipts.AddRange(State.Receipts); return statsState2; } public static string SelfTest() { StatsLedger statsLedger = new StatsLedger(1uL, ""); if (!statsLedger.ApplyPaidSettlement(new StatsSettlement { ReceiptId = "selftest:1", RunId = "selftest-run", Stream = "selftest", Sequence = 1L, Player = 1uL, Game = 0, Bet = new StatsAmount(1.0, 400L), Payout = new StatsAmount(3.0, 400L) }, out var _) || statsLedger.Summarize(1uL, -1).BigWins != 1 || statsLedger.Summarize(1uL, -1).GrossProfit.Exponent != 400) { throw new InvalidOperationException("Statistics large-cash or 3x payout policy failed."); } Validate(statsLedger.Snapshot()); return "PASS: paid settlement/3x big-win/large-cash statistics policy"; } public void Restore(StatsState state) { Validate(state); if (state.Owner != State.Owner || !string.Equals(state.RunId, State.RunId, StringComparison.Ordinal)) { throw new ArgumentException("Statistics owner or run differs."); } List list = new List(); List list2 = new List(); List list3 = new List(state.Receipts); foreach (StatsRow row in state.Rows) { list.Add(Copy(row)); } foreach (StatsStreamState stream in state.Streams) { list2.Add(new StatsStreamState { Stream = stream.Stream, Sequence = stream.Sequence }); } State.Schema = state.Schema; State.Revision = state.Revision; State.Rows.Clear(); State.Streams.Clear(); State.Receipts.Clear(); rows.Clear(); streams.Clear(); receipts.Clear(); players.Clear(); foreach (StatsRow item in list) { State.Rows.Add(item); rows.Add(Key(item.Player, item.Game), item); players.Add(item.Player); } foreach (StatsStreamState item2 in list2) { State.Streams.Add(item2); streams.Add(item2.Stream, item2); } foreach (string item3 in list3) { State.Receipts.Add(item3); receipts.Add(item3); } } public static void Validate(StatsState state) { if (state == null || state.Schema != 1 || state.Revision < 0 || state.RunId == null || ((state.Owner == 0) ? (!ValidId(state.RunId)) : ((byte)state.RunId.Length != 0)) || state.Rows == null || state.Rows.Count > 544 || state.Streams == null || state.Streams.Count > 128 || state.Receipts == null || state.Receipts.Count > 2048) { throw new ArgumentException("Invalid statistics state bounds or scope."); } HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet hashSet2 = new HashSet(StringComparer.Ordinal); HashSet hashSet3 = new HashSet(StringComparer.Ordinal); HashSet hashSet4 = new HashSet(); long num = 0L; foreach (StatsRow row in state.Rows) { if (row == null || row.Player == 0 || (state.Owner != 0 && row.Player != state.Owner) || row.Game < 0 || row.Game >= 17 || !hashSet.Add(Key(row.Player, row.Game)) || row.PlayerName == null || row.PlayerName != CleanName(row.PlayerName) || row.Plays < 0 || row.Wins < 0 || row.Losses < 0 || row.Ties < 0 || row.BigWins < 0 || row.Wins > row.Plays || row.Losses > row.Plays - row.Wins || row.Ties != row.Plays - row.Wins - row.Losses || row.BigWins > row.Wins || row.WinStreak < 0 || row.LossStreak < 0 || row.BestWinStreak < row.WinStreak || row.BestLossStreak < row.LossStreak || row.BestWinStreak > row.Wins || row.BestLossStreak > row.Losses || (row.WinStreak != 0 && row.LossStreak != 0) || !Nonnegative(row.Wager) || !Nonnegative(row.Payout) || !Nonnegative(row.GrossProfit) || !Nonnegative(row.GrossLoss) || !Nonnegative(row.LargestNetWin) || !Nonnegative(row.LargestStake) || !StatsAmount.IsValid(row.Net) || StatsAmount.Compare(row.Net, StatsAmount.Subtract(row.GrossProfit, row.GrossLoss)) != 0 || StatsAmount.Compare(row.LargestStake, row.Wager) > 0 || StatsAmount.Compare(row.LargestNetWin, row.GrossProfit) > 0) { throw new ArgumentException("Invalid statistics aggregate row."); } hashSet4.Add(row.Player); if (row.Plays == 0 || row.Plays > state.Revision - num) { throw new ArgumentException("Statistics play totals exceed revision."); } num += row.Plays; } if (hashSet4.Count > 32) { throw new ArgumentException("Too many statistics players."); } if (num != state.Revision || state.Receipts.Count != Math.Min(state.Revision, 2048L) || ((state.Revision == 0) ? ((byte)state.Streams.Count != 0) : (state.Streams.Count == 0))) { throw new ArgumentException("Statistics journal and aggregate totals differ."); } foreach (StatsStreamState stream in state.Streams) { if (stream == null || !ValidId(stream.Stream) || stream.Sequence <= 0 || !hashSet2.Add(stream.Stream)) { throw new ArgumentException("Invalid statistics stream."); } } foreach (string receipt in state.Receipts) { if (!ValidId(receipt) || !hashSet3.Add(receipt)) { throw new ArgumentException("Invalid statistics receipt."); } } } private static bool Nonnegative(StatsAmount amount) { if (StatsAmount.IsValid(amount)) { return amount.Mantissa >= 0.0; } return false; } private static StatsRow Copy(StatsRow row) { StatsRow statsRow = new StatsRow(); statsRow.Player = row.Player; statsRow.Game = row.Game; statsRow.PlayerName = row.PlayerName; statsRow.Plays = row.Plays; statsRow.Wins = row.Wins; statsRow.Losses = row.Losses; statsRow.Ties = row.Ties; statsRow.BigWins = row.BigWins; statsRow.Wager = row.Wager; statsRow.Payout = row.Payout; statsRow.Net = row.Net; statsRow.GrossProfit = row.GrossProfit; statsRow.GrossLoss = row.GrossLoss; statsRow.LargestNetWin = row.LargestNetWin; statsRow.LargestStake = row.LargestStake; statsRow.WinStreak = row.WinStreak; statsRow.LossStreak = row.LossStreak; statsRow.BestWinStreak = row.BestWinStreak; statsRow.BestLossStreak = row.BestLossStreak; return statsRow; } } [HarmonyPatch] internal static class StatsPayoutScopePatch { private static IEnumerable TargetMethods() { foreach (MethodInfo method in AccessTools.GetDeclaredMethods(typeof(GameBase))) { if (method.Name == "Payout") { yield return method; } } } private static void Prefix(GameBase __instance, out GameBase __state) { __state = Plugin.StatsResultContext; Plugin.StatsResultContext = __instance; } private static Exception Finalizer(Exception __exception, GameBase __state) { Plugin.StatsResultContext = __state; return __exception; } } [HarmonyPatch(typeof(GameResultsManager), "ClearResults")] internal static class StatsClearWagersPatch { private static void Prefix() { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.ClearStatsWagers(); } } } [Serializable] internal sealed class StatsFileEnvelope { public int Schema = 1; public string Payload; public string Sha256; } public sealed class StatsStore { public const int MaximumFileBytes = 2097152; public const int MaximumPayloadCharacters = 1048576; private readonly string path; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static readonly JsonSerializerSettings Settings; public string FilePath => path; public StatsStore(string absolutePath) { if (string.IsNullOrWhiteSpace(absolutePath) || !Path.IsPathRooted(absolutePath)) { throw new ArgumentException("Statistics store needs an absolute file path."); } path = Path.GetFullPath(absolutePath); if (string.IsNullOrEmpty(Path.GetFileName(path)) || string.IsNullOrEmpty(Path.GetDirectoryName(path))) { throw new ArgumentException("Statistics store needs a file, not a directory."); } } public StatsState Load(out string reason) { if (TryLoad(path, out var state, out var reason2)) { reason = "Statistics loaded."; return state; } if (TryLoad(path + ".previous", out state, out var reason3)) { reason = "Recovered the previous statistics checkpoint; the newest uncommitted settlements may be absent."; return state; } if (!File.Exists(path) && !File.Exists(path + ".previous")) { reason = "No recorded statistics yet."; return null; } reason = "Statistics could not be recovered: " + reason2 + " / " + reason3; throw new InvalidDataException(reason); } public void Write(StatsState state) { StatsLedger.Validate(state); string text = JsonConvert.SerializeObject((object)state, (Formatting)0, Settings); if (text.Length > 1048576) { throw new InvalidDataException("Statistics payload bound reached."); } StatsFileEnvelope statsFileEnvelope = new StatsFileEnvelope(); statsFileEnvelope.Payload = text; statsFileEnvelope.Sha256 = Hash(text); string s = JsonConvert.SerializeObject((object)statsFileEnvelope, (Formatting)0, Settings); byte[] bytes = Utf8.GetBytes(s); if (bytes.Length > 2097152) { throw new InvalidDataException("Statistics file bound reached."); } Directory.CreateDirectory(Path.GetDirectoryName(path)); string text2 = path + ".pending-" + Guid.NewGuid().ToString("N"); try { using (FileStream fileStream = new FileStream(text2, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { fileStream.Write(bytes, 0, bytes.Length); fileStream.Flush(flushToDisk: true); } if (!TryLoad(text2, out var state2, out var reason) || state2.Owner != state.Owner || state2.RunId != state.RunId || state2.Revision != state.Revision) { throw new IOException("Statistics write failed readback verification: " + reason); } if (File.Exists(path)) { if (TryLoad(path, out var state3, out var _)) { if (state3.Owner != state.Owner) { throw new InvalidDataException("Statistics store belongs to another local owner."); } File.Replace(text2, path, path + ".previous", ignoreMetadataErrors: true); } else { string destFileName = path + ".corrupt-" + DateTime.UtcNow.ToString("yyyyMMddTHHmmssfff", CultureInfo.InvariantCulture) + "-" + Guid.NewGuid().ToString("N"); File.Move(path, destFileName); File.Move(text2, path); } } else { File.Move(text2, path); } if (!TryLoad(path, out state2, out reason) || state2.Owner != state.Owner || state2.RunId != state.RunId || state2.Revision != state.Revision) { throw new IOException("Statistics atomic commit could not be verified: " + reason); } } finally { if (File.Exists(text2)) { File.Delete(text2); } } } public void Flush(StatsState state) { Write(state); } public bool TryWrite(StatsState state, out string reason) { try { Write(state); reason = "Statistics saved."; return true; } catch (Exception ex) { reason = "Statistics were not saved: " + ex.GetBaseException().Message; return false; } } private static bool TryLoad(string candidate, out StatsState state, out string reason) { state = null; reason = "File is absent."; if (!File.Exists(candidate)) { return false; } try { byte[] array; using (FileStream fileStream = new FileStream(candidate, FileMode.Open, FileAccess.Read, FileShare.Read)) { if (fileStream.Length <= 0 || fileStream.Length > 2097152) { throw new InvalidDataException("Invalid statistics file size."); } array = new byte[(int)fileStream.Length]; int num; for (int i = 0; i < array.Length; i += num) { num = fileStream.Read(array, i, array.Length - i); if (num == 0) { throw new EndOfStreamException(); } } if (fileStream.ReadByte() != -1) { throw new InvalidDataException("Statistics file changed while being read."); } } StatsFileEnvelope statsFileEnvelope = JsonConvert.DeserializeObject(Utf8.GetString(array), Settings); if (statsFileEnvelope == null || statsFileEnvelope.Schema != 1 || string.IsNullOrEmpty(statsFileEnvelope.Payload) || statsFileEnvelope.Payload.Length > 1048576 || !ValidHash(statsFileEnvelope.Sha256) || !string.Equals(statsFileEnvelope.Sha256, Hash(statsFileEnvelope.Payload), StringComparison.Ordinal)) { throw new InvalidDataException("Statistics checksum or schema is invalid."); } StatsState statsState = JsonConvert.DeserializeObject(statsFileEnvelope.Payload, Settings); StatsLedger.Validate(statsState); state = statsState; reason = "Valid statistics."; return true; } catch (Exception ex) { reason = ex.GetBaseException().Message; return false; } } private static string Hash(string text) { using SHA256 sHA = SHA256.Create(); byte[] array = sHA.ComputeHash(Utf8.GetBytes(text)); StringBuilder stringBuilder = new StringBuilder(64); byte[] array2 = array; foreach (byte b in array2) { stringBuilder.Append(b.ToString("x2", CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } private static bool ValidHash(string value) { if (value == null || value.Length != 64) { return false; } foreach (char c in value) { if ((c < '0' || c > '9') && (c < 'a' || c > 'f')) { return false; } } return true; } static StatsStore() { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0012: Expected O, but got Unknown JsonSerializerSettings val = new JsonSerializerSettings(); val.TypeNameHandling = (TypeNameHandling)0; val.MaxDepth = 32; val.CheckAdditionalContent = true; val.MissingMemberHandling = (MissingMemberHandling)0; Settings = val; } } [Serializable] public sealed class StatsQuery { public int RequestId; public int Scope; public ulong PlayerId; } [Serializable] public sealed class StatsPlayerOption { public ulong Id; public string Name; public bool CurrentRunAvailable = true; public bool LifetimeAvailable; } [Serializable] public sealed class StatsTotals { public long Played; public long Wins; public long Losses; public long Ties; public long BigWins; public string Net; public string PositiveNet; public string NegativeNet; public string Wagered; public string Payout; public string BestNetWin; public string LargestBet; public string WinRate; public string BigWinRate; } [Serializable] public sealed class StatsGameView { public int GameId; public string Name; public StatsTotals Totals; } [Serializable] public sealed class StatsView { public int RequestId; public int Scope; public ulong PlayerId; public ulong LocalPlayerId; public string Status; public string PeriodLabel; public string PrivacyHint; public StatsPlayerOption[] Players; public StatsTotals Summary; public StatsGameView[] Games; } public sealed class StatsWagers { private sealed class Charge { public int Source; public int Game; public StatsAmount Bet; } private readonly Dictionary> charges = new Dictionary>(); public bool Add(ulong player, int source, int game, StatsAmount bet) { if (player == 0 || source == 0 || game < 0 || game >= 17 || !StatsAmount.IsValid(bet) || bet.Mantissa <= 0.0) { return false; } if (!charges.TryGetValue(player, out var value)) { if (charges.Count >= 32) { return false; } value = new List(); charges.Add(player, value); } if (value.Count >= 32) { return false; } value.Add(new Charge { Source = source, Game = game, Bet = bet }); return true; } public bool Consume(ulong player, int source, int game, StatsAmount bet) { if (!StatsAmount.IsValid(bet) || bet.Mantissa <= 0.0 || !charges.TryGetValue(player, out var value)) { return false; } StatsAmount left = StatsAmount.Zero; List list = new List(); foreach (Charge item in value) { if (item.Source != source || item.Game != game) { continue; } left = StatsAmount.Add(left, item.Bet); list.Add(item); int num = StatsAmount.Compare(left, bet); if (num > 0) { return false; } if (num != 0) { continue; } foreach (Charge item2 in list) { value.Remove(item2); } if (value.Count == 0) { charges.Remove(player); } return true; } return false; } public void Cancel(int source) { List list = new List(); foreach (KeyValuePair> charge in charges) { charge.Value.RemoveAll((Charge c) => c.Source == source); if (charge.Value.Count == 0) { list.Add(charge.Key); } } foreach (ulong item in list) { charges.Remove(item); } } public void Clear() { charges.Clear(); } } internal sealed class StatsWriter { private sealed class Job { public StatsStore Store; public StatsState Snapshot; } private readonly object gate = new object(); private readonly Dictionary pending = new Dictionary(StringComparer.Ordinal); private readonly Queue order = new Queue(); private readonly Queue errors = new Queue(); private readonly ManualResetEvent drained = new ManualResetEvent(initialState: true); private bool running; public bool Enqueue(string path, StatsStore store, StatsState snapshot) { lock (gate) { if (!pending.ContainsKey(path)) { if (pending.Count >= 8) { return false; } order.Enqueue(path); } pending[path] = new Job { Store = store, Snapshot = snapshot }; if (!running) { running = true; drained.Reset(); ThreadPool.QueueUserWorkItem(Work); } return true; } } private void Work(object unused) { while (true) { Job job; lock (gate) { if (order.Count == 0) { running = false; drained.Set(); break; } string key = order.Dequeue(); job = pending[key]; pending.Remove(key); } if (job.Store.TryWrite(job.Snapshot, out var reason)) { continue; } lock (gate) { if (errors.Count < 8) { errors.Enqueue(reason); } } } } public string TakeError() { lock (gate) { return (errors.Count == 0) ? null : errors.Dequeue(); } } public bool Finish(int milliseconds) { return drained.WaitOne(milliseconds); } } public static class TimerReturnPolicy { public static bool CanContinue(bool enabled, bool server, bool timerScope, bool sameOwner, bool casino, bool activeDay, bool expired, bool transitioning, bool elevatorBusy, bool partyReady, int currentFloor, int targetFloor) { if (enabled && server && timerScope && sameOwner && casino && activeDay && expired && !transitioning && !elevatorBusy && partyReady && currentFloor == 0) { return targetFloor == 0; } return false; } } internal static class ChaosTimerReturn { [ThreadStatic] internal static GameManager TimerOwner; private static readonly FieldInfo GameInstance = AccessTools.Field(typeof(GameManager), "_instance"); private static readonly FieldInfo ElevatorInstance = AccessTools.Field(typeof(ElevatorManager), "_instance"); private static readonly FieldInfo Expired = AccessTools.Field(typeof(GameManager), "_isTimeOver"); private static readonly FieldInfo Transitioning = AccessTools.Field(typeof(GameManager), "_isTransitioning"); internal static readonly FieldInfo Floor = AccessTools.Field(typeof(ElevatorManager), "_currentFloorIndex"); internal static void PrepareResumedTimer(object coroutine) { //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_006d: Invalid comparison between Unknown and I4 //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008e: Unknown result type (might be due to invalid IL or missing references) Plugin instance = Plugin.Instance; if ((Object)(object)instance == (Object)null) { return; } try { int num = (int)AccessTools.Field(coroutine.GetType(), "<>1__state").GetValue(coroutine); object? value = GameInstance.GetValue(null); GameManager val = (GameManager)((value is GameManager) ? value : null); if (num == 1 && object.ReferenceEquals(val, TimerOwner) && !((Object)(object)val == (Object)null) && ((NetworkBehaviour)val).isServer && NetworkServer.active && (int)val.state == 1 && val.HasDayStarted && (bool)Expired.GetValue(val)) { Scene activeScene = SceneManager.GetActiveScene(); if (!(((Scene)(ref activeScene)).name != "CasinoScene")) { object? value2 = ElevatorInstance.GetValue(null); instance.PrepareExpiredTimerItems((ElevatorManager)((value2 is ElevatorManager) ? value2 : null)); instance.TraceTimerReturn("Expired timer: native item cleanup and return sequence resumed."); } } } catch (Exception ex) { instance.TraceTimerReturn("Timer preflight kept native behavior: " + ex.GetBaseException().Message); } } private static bool ServerSpawned(NetworkIdentity identity) { if ((Object)(object)identity != (Object)null && identity.netId != 0 && NetworkServer.spawned.TryGetValue(identity.netId, out var value)) { return object.ReferenceEquals(value, identity); } return false; } internal static MethodBase TimerAdvance() { Type type = null; Type[] nestedTypes = typeof(GameManager).GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic); foreach (Type type2 in nestedTypes) { if (type2.Name.StartsWith("", StringComparison.Ordinal)) { if (type != null) { throw new InvalidOperationException("Timer coroutine is ambiguous."); } type = type2; } } MethodInfo methodInfo = ((type == null) ? null : AccessTools.Method(type, "MoveNext", (Type[])null, (Type[])null)); FieldInfo fieldInfo = ((type == null) ? null : AccessTools.Field(type, "<>4__this")); if (methodInfo == null || methodInfo.ReturnType != typeof(bool) || fieldInfo == null || fieldInfo.FieldType != typeof(GameManager)) { throw new InvalidOperationException("Native timer coroutine shape changed."); } return methodInfo; } internal static bool ContinueSameFloor(ElevatorManager elevator, int targetFloor) { //IL_00ec: 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_010e: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Invalid comparison between Unknown and I4 //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0090: Unknown result type (might be due to invalid IL or missing references) try { Plugin instance = Plugin.Instance; object? value = GameInstance.GetValue(null); GameManager val = (GameManager)((value is GameManager) ? value : null); int num; if ((Object)(object)val != (Object)null && (Object)(object)elevator != (Object)null && ((NetworkBehaviour)val).isServer && ((NetworkBehaviour)elevator).isServer && object.ReferenceEquals(val, TimerOwner) && object.ReferenceEquals(ElevatorInstance.GetValue(null), elevator) && ServerSpawned(((NetworkBehaviour)val).netIdentity) && ServerSpawned(((NetworkBehaviour)elevator).netIdentity)) { Scene scene = ((Component)elevator).gameObject.scene; SceneHandle handle = ((Scene)(ref scene)).handle; Scene activeScene = SceneManager.GetActiveScene(); num = ((handle == ((Scene)(ref activeScene)).handle) ? 1 : 0); } else { num = 0; } bool flag = (byte)num != 0; bool server = NetworkServer.active && NetworkServer.localConnection != null && ((NetworkConnection)NetworkServer.localConnection).isAuthenticated && ((NetworkConnection)NetworkServer.localConnection).isReady; bool flag2 = flag && (Object)(object)instance != (Object)null && instance.TimerReturnAllowed; bool timerScope = (Object)(object)TimerOwner != (Object)null; Scene activeScene2 = SceneManager.GetActiveScene(); bool flag3 = TimerReturnPolicy.CanContinue(flag2, server, timerScope, flag, ((Scene)(ref activeScene2)).name == "CasinoScene", (Object)(object)val != (Object)null && (int)val.state == 1 && val.HasDayStarted, (Object)(object)val != (Object)null && (bool)Expired.GetValue(val), (Object)(object)val == (Object)null || (bool)Transitioning.GetValue(val), (Object)(object)elevator == (Object)null || elevator.isTeleporting, flag2, ((Object)(object)elevator == (Object)null) ? (-1) : ((int)Floor.GetValue(elevator)), targetFloor); if (flag3) { instance.TraceTimerReturn("Expired timer at floor 0: continuing the native elevator and return-limo sequence."); } return flag3; } catch (Exception ex) { if ((Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.TraceTimerReturn("Same-floor return kept native guard: " + ex.GetBaseException().Message); } return false; } } internal static IEnumerable PreserveNativeReturn(IEnumerable original) { //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Expected O, but got Unknown //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_015b: Expected O, but got Unknown //IL_019d: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown //IL_01c1: Unknown result type (might be due to invalid IL or missing references) //IL_01c7: Expected O, but got Unknown //IL_01d1: Unknown result type (might be due to invalid IL or missing references) //IL_01d7: Expected O, but got Unknown List list = new List(); foreach (CodeInstruction item in original) { list.Add(new CodeInstruction(item)); } int num = -1; for (int i = 2; i + 2 < list.Count; i++) { if (list[i - 1].opcode == OpCodes.Ldarg_0 && list[i].opcode == OpCodes.Ldfld && object.Equals(list[i].operand, Floor) && list[i + 1].opcode == OpCodes.Ldarg_1 && (list[i + 2].opcode == OpCodes.Bne_Un_S || list[i + 2].opcode == OpCodes.Bne_Un) && i + 3 < list.Count && list[i + 3].opcode == OpCodes.Ret) { if (num >= 0) { throw new InvalidOperationException("Native same-floor return is ambiguous."); } num = i + 3; } } if (num < 0) { throw new InvalidOperationException("Verified native same-floor timer return was not found."); } object operand = list[num - 1].operand; CodeInstruction val = new CodeInstruction(OpCodes.Ldarg_0, (object)null); val.labels.AddRange(list[num].labels); list[num].labels.Clear(); list.InsertRange(num, (IEnumerable)(object)new CodeInstruction[4] { val, new CodeInstruction(OpCodes.Ldarg_1, (object)null), new CodeInstruction(OpCodes.Call, (object)AccessTools.Method(typeof(ChaosTimerReturn), "ContinueSameFloor", (Type[])null, (Type[])null)), new CodeInstruction(OpCodes.Brtrue, operand) }); return list; } } [HarmonyPatch] internal static class TimerReturnNativeScopePatch { private static MethodBase TargetMethod() { return ChaosTimerReturn.TimerAdvance(); } private static void Prefix(object __instance, out GameManager __state) { __state = ChaosTimerReturn.TimerOwner; object? value = AccessTools.Field(__instance.GetType(), "<>4__this").GetValue(__instance); ChaosTimerReturn.TimerOwner = (GameManager)((value is GameManager) ? value : null); ChaosTimerReturn.PrepareResumedTimer(__instance); } private static Exception Finalizer(Exception __exception, GameManager __state) { ChaosTimerReturn.TimerOwner = __state; if (__exception != null && (Object)(object)Plugin.Instance != (Object)null) { Plugin.Instance.TraceTimerReturn("Native timer stopped: " + __exception); } return __exception; } } [HarmonyPatch(typeof(ElevatorManager), "ServerForceTeleportPlayers")] internal static class TimerReturnSameFloorPatch { private static IEnumerable Transpiler(IEnumerable instructions) { return ChaosTimerReturn.PreserveNativeReturn(instructions); } } [Serializable] public sealed class UiProgress { public bool Available; public double Fraction; public string Caption; public bool Complete; public bool Failed; } internal static class UiProgressPolicy { internal static bool IsVisible(UiProgress value) { if (value != null && value.Available && !double.IsNaN(value.Fraction)) { return !double.IsInfinity(value.Fraction); } return false; } internal static double Fill(UiProgress value) { if (!IsVisible(value)) { return 0.0; } return Math.Max(0.0, Math.Min(1.0, value.Fraction)); } internal static double PanelScale(double width, double height) { if (double.IsNaN(width) || double.IsInfinity(width) || double.IsNaN(height) || double.IsInfinity(height) || width <= 40.0 || height <= 40.0) { return 1.0; } return Math.Min(1.0, Math.Min((width - 40.0) / 740.0, (height - 40.0) / 640.0)); } } [Serializable] public sealed class UiState { public bool IsHost; public bool Enabled; public bool GameplayActive; public bool AllInEnabled; public bool AllInAvailable; public bool SessionReloading; public bool PoliceBailVisible; public bool PoliceBailCanContinue; public long PoliceBailRequestId; public long PoliceBailRevision; public int PoliceBailDay; public string PoliceBailFee; public string PoliceBailOriginal; public string PoliceBailRemaining; public string PoliceBailStatus; public ulong[] PoliceBailCrew; public ulong[] PoliceBailContinued; public ulong[] PoliceBailWaived; public string Status; public string Round; public string Modifiers; public string HudModifiers; public string HudRisk; public string HudGoal; public bool HudRiskActive; public string NegativeModifiers; public string RiskLimits; public string HudNegatives; public string Contract; public string Perks; public string Summary; public string ActionHint; public string RewardSummary; public string CashSummary; public UiProgress[] ModifierProgress; public UiProgress[] RiskProgress; public UiProgress GoalProgress; public UiProgress CashTargetProgress; public UiProgress BonusCapProgress; public string RerollModifierHint; public string RerollContractHint; public string RerollPerksHint; public string SettingsHint; public string[] Choices; public int Intensity; public float JackpotPercent; public float BonusCap; public bool ClosingTimeEnabled; public bool ContractsEnabled; public bool NegativesEnabled = true; public bool TimedNegativesEnabled; public int PenaltySeverity = 2; public float RiskDayPercent = 50f; public bool ReducedMotion; public string Preset; public bool CanRerollModifier; public bool CanRerollContract; public bool CanRerollPerks; public bool VoteVisible; public bool VoteActive; public bool VoteClosed; public bool CanVote; public bool CanCancelVote; public bool CanRequestRestartVote; public bool CanStartFreshRun; public bool CanRepairAllBodies; public string BodyRepairHint; public int VoteYes; public int VoteNo; public int VotePlayers; public int VoteRequired; public int VoteId; public string VoteEpoch; public ulong[] VoteMembers; public ulong[] VoteVoters; public int FreshRunMinDay; public int FreshRunMaxDay; public string VoteStatus; public string VoteHint; public string RestartVoteHint; public string FreshRunHint; public string FreshRunDefaultDay; public string FreshRunDefaultMoney; } internal sealed class TodayTaskCopy { internal readonly string Title; internal readonly string Action; internal readonly string Details; internal readonly bool HasBody; private TodayTaskCopy(string record) { int num = record.IndexOf('\n'); HasBody = num >= 0; Title = ((num < 0) ? record : record.Substring(0, num)); if (num < 0) { Action = ""; Details = ""; } else { int num2 = record.IndexOf('\n', num + 1); Action = ((num2 < 0) ? record.Substring(num + 1) : record.Substring(num + 1, num2 - num - 1)); Details = ((num2 < 0) ? "" : record.Substring(num2 + 1)); } } internal static TodayTaskCopy[] Split(string source) { if (string.IsNullOrEmpty(source)) { return new TodayTaskCopy[0]; } string text = source.Replace("\r\n", "\n").Replace('\r', '\n').Trim('\n'); if (text.Length == 0) { return new TodayTaskCopy[0]; } string[] array = text.Split(new string[1] { "\n\n" }, 8, StringSplitOptions.RemoveEmptyEntries); TodayTaskCopy[] array2 = new TodayTaskCopy[array.Length]; for (int i = 0; i < array.Length; i++) { array2[i] = new TodayTaskCopy(array[i]); } return array2; } } internal sealed class CashOverviewCopy { internal string Target = "Shown in the casino"; internal string Cap = "Shown in the casino"; internal string ChangeLabel = "Chaos cash change"; internal string Change = "—"; internal string Penalties = "—"; internal string NpcCash = "—"; internal string Note = "Cash totals appear when you enter the casino."; internal bool HasTotals; internal static CashOverviewCopy Parse(string source) { CashOverviewCopy cashOverviewCopy = new CashOverviewCopy(); if (string.IsNullOrEmpty(source)) { return cashOverviewCopy; } string[] array = source.Replace("\r\n", "\n").Replace('\r', '\n').Split('\n'); StringBuilder stringBuilder = new StringBuilder(); foreach (string text in array) { if (text.StartsWith("Today's target: ", StringComparison.Ordinal)) { cashOverviewCopy.Target = text.Substring(16); cashOverviewCopy.HasTotals = true; } else if (text.StartsWith("Daily casino-bonus cap: ", StringComparison.Ordinal)) { cashOverviewCopy.Cap = text.Substring("Daily casino-bonus cap: ".Length); } else if (text.StartsWith("Chaos cash change: ", StringComparison.Ordinal)) { cashOverviewCopy.Change = text.Substring(19); } else if (text.StartsWith("Earlier bonuses: ", StringComparison.Ordinal)) { cashOverviewCopy.ChangeLabel = "Earlier bonuses"; cashOverviewCopy.Change = text.Substring(17); } else if (text.StartsWith("Risk penalties paid: ", StringComparison.Ordinal)) { cashOverviewCopy.Penalties = text.Substring(21); } else if (text.StartsWith("NPC cash earned: ", StringComparison.Ordinal)) { cashOverviewCopy.NpcCash = text.Substring(17); } else if (text.Length > 0) { if (stringBuilder.Length > 0) { stringBuilder.Append('\n'); } stringBuilder.Append(text); } } if (stringBuilder.Length > 0) { cashOverviewCopy.Note = stringBuilder.ToString(); } else if (cashOverviewCopy.HasTotals) { cashOverviewCopy.Note = "Casino winnings and NPC cash are outside the bonus cap."; } return cashOverviewCopy; } } public sealed class ChaosUi : IDisposable { private sealed class CompletionMessage { public string Status; public string Title; public string Body; public bool Penalty; public bool GunPurchase; } private sealed class HudSection { public RectTransform Root; public Image Surface; public Text Heading; public Text Body; public string SourceText; public ProgressVisual Progress; } private sealed class TabArea { public GameObject Root; public RectTransform Content; public ScrollRect Scroll; public Scrollbar Bar; public float Height; public float Width; public readonly List Items = new List(); } private sealed class FlowItem { public RectTransform Rect; public Text Text; public float FixedHeight; public float MinimumHeight; public float TopGap; public Vector2 TextOffset; public float BottomPadding; public TabArea Section; public float SectionPadding = 28f; public StatsCard Stats; public ProgressVisual Progress; public ChoiceCard Choice; } private sealed class TodayGroup { public TabArea Area; public Text Heading; public Text Empty; public bool Risk; public string Source; public string EmptySource; public readonly List Cards = new List(); } private sealed class TodayCard { public TabArea Area; public Text Title; public Text Action; public Text Details; public ProgressVisual Progress; } private sealed class ProgressVisual { public RectTransform Root; public RectTransform Track; public RectTransform Fill; public Image FillImage; public Text Caption; public bool Risk; } private sealed class StatsCard { public RectTransform Root; public RectTransform ArtRect; public Image Art; public Text NoArt; public Text ArtCaption; public Text Title; public Text Context; public Text NetLabel; public Text Net; public RectTransform Divider; public readonly Text[] CategoryHeadings = (Text[])(object)new Text[3]; public readonly Text[] MetricLabels = (Text[])(object)new Text[13]; public readonly Text[] MetricValues = (Text[])(object)new Text[13]; public ProgressVisual WinRate; public ProgressVisual BigWinRate; public Text Help; public bool HasArt; public int GameId = -1; } private sealed class ChoiceCard { public Text Title; public Text Category; public Text Description; } private static readonly FieldInfo NativeCursorInstance = typeof(UICursorSimple).GetField("_instance", BindingFlags.Static | BindingFlags.NonPublic); private static readonly FieldInfo NativeCursorImage = typeof(BaseCursor).GetField("uiCursorImage", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo NativeCursorVisibility = typeof(UICursorSimple).GetField("_cursorShouldBeVisible", BindingFlags.Instance | BindingFlags.NonPublic); private static readonly FieldInfo NativeUsesUiCursor = typeof(BaseCursor).GetField("useUiCursor", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); private readonly Action _action; private readonly Action _setting; private readonly Action _freshRun; private readonly Color _gold = new Color(0.94f, 0.75f, 0.39f, 1f); private readonly Color _ink = new Color(0.045f, 0.057f, 0.073f, 0.97f); private readonly Color _paper = new Color(0.94f, 0.94f, 0.91f, 1f); private readonly Color _muted = new Color(0.66f, 0.71f, 0.75f, 1f); private GameObject _root; private CanvasGroup _hudGroup; private CanvasGroup _panelGroup; private GameObject _panelContainer; private RectTransform _panelBox; private double _panelCanvasWidth; private double _panelCanvasHeight; private Font _font; private Text _hudStatus; private Text _hudRound; private Text _hudBody; private RectTransform _hudBox; private Text _hudFooter; private Toggle _showHud; private Transform _nativeUpgradeBounds; private double _hudCanvasWidth; private double _hudCanvasHeight; private double _hudNativeBottom = -1.0; private double _hudMaximumHeight = 334.0; private HudSection _hudModifierSection; private HudSection _hudRiskSection; private HudSection _hudGoalSection; private float _nextRiskPulse; private bool _hudHasSafePlacement = true; private readonly Vector3[] _nativeHudCorners = (Vector3[])(object)new Vector3[4]; private Text _round; private Text _status; private Text _negativeHeading; private TodayGroup _modifierCards; private TodayGroup _riskCards; private TodayGroup _goalCards; private Text _riskLimits; private TodayGroup _perkCards; private Text _summary; private Text _actionHint; private Text _rewardSummary; private Text _cashTarget; private Text _cashCap; private Text _cashChangeLabel; private Text _cashChange; private Text _cashPenalties; private Text _cashNpc; private Text _cashNote; private string _cashSource; private ProgressVisual _cashTargetBar; private ProgressVisual _cashCapBar; private Text _rerollModifierHint; private Text _rerollContractHint; private Text _rerollPerksHint; private Text _choiceHint; private Text _choiceHeading; private Text _hostHint; private Text _intensityLabel; private Text _jackpotLabel; private Text _bonusLabel; private Text _presetLabel; private Text _penaltySeverityLabel; private Text _riskChanceLabel; private Text _restartVoteHint; private Text _cancelVoteHint; private Text _freshRunHint; private Text _bodyRepairHint; private Text _freshDayLabel; private GameObject _voteOverlay; private RectTransform _voteBox; private Text _voteStatus; private Text _voteTally; private Text _voteHint; private Button _voteYes; private Button _voteNo; private Button _dismissVote; private string _dismissedVoteEpoch; private int _dismissedVoteId; private Button _requestRestartVote; private Button _cancelRestartVote; private Button _applyFreshRun; private Button _repairAllBodies; private InputField _freshDay; private InputField _freshMoney; private Text _freshMoneyRecommendation; private bool _freshFieldsInitialized; private Toggle _enabled; private Toggle _closingTime; private Toggle _contracts; private Toggle _negatives; private Toggle _timedNegatives; private Toggle _reducedMotion; private Slider _intensity; private Slider _jackpot; private Slider _bonus; private Slider _penaltySeverity; private Slider _riskChance; private GameObject _completionOverlay; private RectTransform _completionBox; private CanvasGroup _completionGroup; private Text _completionStatus; private Text _completionTitle; private Text _completionBody; private Image _completionAccent; private float _completionExpires; private int _completionScene = -1; private CompletionMessage _completionCurrent; private readonly Queue _completionQueue = new Queue(8); private readonly Button[] _choices = (Button[])(object)new Button[3]; private readonly Text[] _choiceText = (Text[])(object)new Text[3]; private readonly Text[] _choiceCategory = (Text[])(object)new Text[3]; private readonly Text[] _choiceDescription = (Text[])(object)new Text[3]; private readonly Button[] _presets = (Button[])(object)new Button[3]; private Button _rerollModifier; private Button _rerollContract; private Button _rerollPerks; private Button _close; private Button _todayTab; private Button _settingsTab; private Button _detailsTab; private Button _statsTab; private Button _statsRunScope; private Button _statsLifetimeScope; private Button _statsRefresh; private Dropdown _statsPlayerFilter; private Dropdown _statsGameFilter; private readonly List _statsGameIds = new List(); private int _statsSelectedGame = -1; private Button _statsPrevious; private Button _statsNext; private Button _statsOverview; private Text _panelFooter; private Text _statsStatus; private Text _statsPeriod; private Text _statsPrivacy; private Text _statsEmpty; private StatsCard _statsSummary; private readonly StatsCard[] _statsCards = new StatsCard[17]; private readonly List _statsPlayers = new List(); private Action _statsRequest; private Func _statsNativeArt; private Func _statsArtSource; private StatsView _statsView; private ulong _statsLocalPlayerId; private ulong _statsPlayerId; private int _statsScope; private int _statsRequestId; private bool _statsPresenting; private bool _statsLoading; private bool _statsConfigured; private int _selectedTab; private bool _presenting; private bool _hudWanted = true; private bool _disposed; private bool _cursorCaptured; private CursorLockMode _savedCursorLock; private bool _savedCursorVisible; private UICursorSimple _nativeCursor; private Image _nativeCursorImage; private bool _savedNativeCursorImageEnabled; private bool _savedNativeCursorVisible; private bool _nativeVisibilityCaptured; private float _panelTarget; private UiState _state; private int _closedFrame = -1; private GameObject _previousSelection; private EventSystem _selectionEventSystem; private bool _selectionNeedsBind; private bool _selectionCaptured; private bool _legacyInputAvailable = true; private bool _pendingMenuPress; private bool _pendingHudPress; private bool _pendingEscapePress; private bool _pendingVoteYesPress; private bool _pendingVoteNoPress; private uint _lastMenuUpdate = uint.MaxValue; private uint _lastHudUpdate = uint.MaxValue; private uint _lastEscapeUpdate = uint.MaxValue; private uint _lastVoteYesUpdate = uint.MaxValue; private uint _lastVoteNoUpdate = uint.MaxValue; private bool _inputSubscribed; private bool _hadLobby; private readonly TabArea[] _tabs = new TabArea[4]; private readonly Dictionary _navigationMarkers = new Dictionary(); private TabArea _settingsRulesAnchor; private TabArea _settingsRiskAnchor; private TabArea _settingsToolsAnchor; private TabArea _settingsScreenAnchor; private TabArea _choiceSection; public bool IsOpen { get; private set; } public bool HudVisible => _hudWanted; public bool StatsVisible { get { if (IsOpen) { return _selectedTab == 3; } return false; } } public int CurrentVoteId { get { if (_state != null) { return _state.VoteId; } return 0; } } public bool WantsWorldInputBlocked { get { if (!IsOpen && (_state == null || !_state.PoliceBailVisible)) { return Time.frameCount == _closedFrame; } return true; } } public bool NativeChallengeSpaceReserved => false; public bool NativeChallengeSpaceSupported => false; public bool HudUsesSeparateColumn => false; public bool HudHasSafePlacement => _hudHasSafePlacement; public void SetHudVisible(bool visible) { if (!_disposed) { _hudWanted = visible; if ((Object)(object)_showHud != (Object)null) { bool presenting = _presenting; _presenting = true; _showHud.isOn = visible; _presenting = presenting; } if ((Object)(object)_root != (Object)null) { RefreshHudVisibility(); } } } private void ToggleHudLocally(bool visible) { if (!_presenting && !_disposed) { SetHudVisible(visible); if (_setting != null) { _setting("hudvisible", visible ? 1f : 0f); } } } public ChaosUi(Action action, Action setting) : this(action, setting, null) { } public ChaosUi(Action action, Action setting, Action freshRun) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0077: Unknown result type (might be due to invalid IL or missing references) _action = action; _setting = setting; _freshRun = freshRun; } public void ConfigureStats(Action request, Func nativeArt, Func artSource = null) { if (!_disposed) { _statsRequest = request; _statsNativeArt = nativeArt; _statsArtSource = artSource; _statsConfigured = request != null; if ((Object)(object)_statsRefresh != (Object)null) { ((Selectable)_statsRefresh).interactable = _statsConfigured; } if ((Object)(object)_statsRunScope != (Object)null) { ((Selectable)_statsRunScope).interactable = _statsConfigured; } if ((Object)(object)_statsLifetimeScope != (Object)null) { ((Selectable)_statsLifetimeScope).interactable = _statsConfigured && _statsLocalPlayerId != 0; } if ((Object)(object)_statsStatus != (Object)null && !_statsConfigured) { _statsStatus.text = "Statistics are unavailable in this session."; } if (IsOpen && _selectedTab == 3) { RequestStats(force: false); } } } public void RefreshStatsArtwork() { if (_disposed || (Object)(object)_root == (Object)null) { return; } for (int i = 0; i < _statsCards.Length; i++) { if (_statsCards[i] != null && ((Component)_statsCards[i].Root).gameObject.activeSelf) { SetStatsArt(_statsCards[i]); } } } public void PresentStats(StatsView view) { if (_disposed || view == null || _statsRequestId <= 0 || view.RequestId != _statsRequestId || view.Scope != _statsScope || view.PlayerId != _statsPlayerId) { return; } if ((Object)(object)_root == (Object)null) { Initialize(); } _statsPresenting = true; try { _statsView = view; _statsLoading = false; _statsLocalPlayerId = view.LocalPlayerId; _statsStatus.text = StatsText(view.Status, "Stats loaded. Use Refresh after more games.", 512); _statsPeriod.text = StatsText(view.PeriodLabel, (view.Scope == 1) ? "Your lifetime" : "Current run", 160); _statsPrivacy.text = StatsText(view.PrivacyHint, "Your lifetime stats stay on this computer across saves and hosts. Other players' lifetime stats are private.", 512); ((Component)_statsPrivacy).gameObject.SetActive(view.Scope == 1); RefreshStatsPlayers(view.Players); ((Selectable)_statsLifetimeScope).interactable = _statsConfigured && _statsLocalPlayerId != 0; ((Selectable)_statsRunScope).interactable = _statsConfigured; ((Selectable)_statsRefresh).interactable = _statsConfigured; StyleTab(_statsRunScope, _statsScope == 0); StyleTab(_statsLifetimeScope, _statsScope == 1); SetStatsCard(_statsSummary, "All casino games", view.Summary); bool[] array = new bool[17]; int num = 0; if (view.Games != null) { for (int i = 0; i < view.Games.Length && i < 17; i++) { StatsGameView statsGameView = view.Games[i]; if (statsGameView != null && statsGameView.GameId >= 0 && statsGameView.GameId < 17 && !array[statsGameView.GameId]) { array[statsGameView.GameId] = true; StatsCard statsCard = _statsCards[statsGameView.GameId]; if (statsCard == null) { statsCard = BuildStatsCard(_tabs[3], statsGameView.GameId, picture: true); _statsCards[statsGameView.GameId] = statsCard; } ((Component)statsCard.Root).gameObject.SetActive(false); SetStatsCard(statsCard, StatsText(statsGameView.Name, "Casino game " + statsGameView.GameId, 96), statsGameView.Totals); num++; } } } for (int j = 0; j < _statsCards.Length; j++) { if (_statsCards[j] != null && !array[j]) { ((Component)_statsCards[j].Root).gameObject.SetActive(false); } } RefreshStatsGames(view.Games); ShowSelectedStats(jump: false); RefreshScrollHeights(); } finally { _statsPresenting = false; } } private void RequestStats(bool force) { if (_disposed || !_statsConfigured || _statsRequest == null || (!force && (_statsLoading || (_statsView != null && _statsView.Scope == _statsScope && _statsView.PlayerId == _statsPlayerId)))) { return; } if (_statsScope == 1 && _statsLocalPlayerId == 0) { _statsStatus.text = "Open Current run first to load your player."; return; } _statsRequestId = ((_statsRequestId == int.MaxValue) ? 1 : (_statsRequestId + 1)); _statsLoading = true; _statsStatus.text = "Loading stats..."; ((Selectable)_statsGameFilter).interactable = false; UpdateStatsNavigation(); if (_statsSummary != null) { ((Component)_statsSummary.Root).gameObject.SetActive(false); } for (int i = 0; i < _statsCards.Length; i++) { if (_statsCards[i] != null) { ((Component)_statsCards[i].Root).gameObject.SetActive(false); } } ((Component)_statsEmpty).gameObject.SetActive(false); RefreshScrollHeights(); try { _statsRequest(new StatsQuery { RequestId = _statsRequestId, Scope = _statsScope, PlayerId = _statsPlayerId }); } catch (Exception) { _statsLoading = false; _statsStatus.text = "Statistics could not be loaded. Try Refresh."; UpdateStatsNavigation(); } } private void SelectStatsScope(int scope) { if (_statsPresenting || scope == _statsScope) { return; } switch (scope) { default: return; case 1: if (_statsLocalPlayerId == 0) { return; } break; case 0: break; } _statsScope = scope; _statsPlayerId = ((scope == 1) ? _statsLocalPlayerId : 0); _statsLoading = false; RefreshStatsPlayers((_statsView == null) ? null : _statsView.Players); StyleTab(_statsRunScope, scope == 0); StyleTab(_statsLifetimeScope, scope == 1); RequestStats(force: true); } private void RefreshStatsPlayers(StatsPlayerOption[] players) { //IL_0226: Unknown result type (might be due to invalid IL or missing references) //IL_0230: Expected O, but got Unknown bool statsPresenting = _statsPresenting; _statsPresenting = true; try { _statsPlayers.Clear(); if (_statsScope == 0) { _statsPlayers.Add(new StatsPlayerOption { Id = 0uL, Name = "Everyone in this run" }); } if (players != null) { for (int i = 0; i < players.Length && i < 32; i++) { StatsPlayerOption statsPlayerOption = players[i]; if (statsPlayerOption == null || statsPlayerOption.Id == 0 || (_statsScope == 0 && !statsPlayerOption.CurrentRunAvailable) || (_statsScope == 1 && statsPlayerOption.Id != _statsLocalPlayerId)) { continue; } bool flag = false; for (int j = 0; j < _statsPlayers.Count; j++) { if (_statsPlayers[j].Id == statsPlayerOption.Id) { flag = true; } } if (!flag) { _statsPlayers.Add(statsPlayerOption); } } } if (_statsScope == 1 && _statsLocalPlayerId != 0 && _statsPlayers.Count == 0) { _statsPlayers.Add(new StatsPlayerOption { Id = _statsLocalPlayerId, Name = "You", LifetimeAvailable = true }); } if (_statsScope == 0 && _statsPlayerId != 0) { bool flag2 = false; for (int k = 0; k < _statsPlayers.Count; k++) { if (_statsPlayers[k].Id == _statsPlayerId) { flag2 = true; } } if (!flag2) { _statsPlayers.Add(new StatsPlayerOption { Id = _statsPlayerId, Name = "Selected player (unavailable)", CurrentRunAvailable = false }); } } List list = new List(); int value = 0; for (int l = 0; l < _statsPlayers.Count; l++) { StatsPlayerOption statsPlayerOption2 = _statsPlayers[l]; string text = StatsText(statsPlayerOption2.Name, (statsPlayerOption2.Id == _statsLocalPlayerId) ? "You" : "Player", 96); list.Add(new OptionData((_statsScope == 1) ? (text + " — your lifetime") : text)); if (statsPlayerOption2.Id == _statsPlayerId) { value = l; } } _statsPlayerFilter.options = list; _statsPlayerFilter.value = value; _statsPlayerFilter.RefreshShownValue(); ((Selectable)_statsPlayerFilter).interactable = _statsConfigured && _statsScope == 0 && _statsPlayers.Count > 1; } finally { _statsPresenting = statsPresenting; } } private void SelectStatsPlayer(int index) { if (!_statsPresenting && _statsScope == 0 && index >= 0 && index < _statsPlayers.Count) { ulong id = _statsPlayers[index].Id; if (id != _statsPlayerId) { _statsPlayerId = id; _statsLoading = false; RequestStats(force: true); } } } private void RefreshStatsGames(StatsGameView[] games) { //IL_00bc: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Expected O, but got Unknown //IL_00fe: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Expected O, but got Unknown _statsGameIds.Clear(); _statsGameIds.Add(-1); List list = new List(); if (games != null) { for (int i = 0; i < games.Length && i < 17; i++) { StatsGameView statsGameView = games[i]; if (statsGameView == null || statsGameView.GameId < 0 || statsGameView.GameId >= _statsCards.Length) { continue; } bool flag = false; for (int j = 0; j < list.Count; j++) { if (list[j].GameId == statsGameView.GameId) { flag = true; } } if (!flag) { list.Add(statsGameView); } } } list.Sort((StatsGameView a, StatsGameView b) => StringComparer.OrdinalIgnoreCase.Compare(a.Name, b.Name)); List list2 = new List(); list2.Add(new OptionData("All games")); for (int num = 0; num < list.Count; num++) { _statsGameIds.Add(list[num].GameId); list2.Add(new OptionData(StatsText(list[num].Name, "Casino game", 96))); } if (!_statsGameIds.Contains(_statsSelectedGame)) { _statsSelectedGame = -1; } _statsGameFilter.options = list2; _statsGameFilter.SetValueWithoutNotify(_statsGameIds.IndexOf(_statsSelectedGame)); _statsGameFilter.RefreshShownValue(); ((Selectable)_statsGameFilter).interactable = _statsConfigured && _statsGameIds.Count > 1; } private void SelectStatsGame(int index) { if (!_statsPresenting && !_statsLoading && index >= 0 && index < _statsGameIds.Count) { SelectStatsGameById(_statsGameIds[index]); } } private void SelectStatsGameById(int game) { if (!_statsLoading && _statsGameIds.Contains(game)) { _statsSelectedGame = game; _statsGameFilter.SetValueWithoutNotify(_statsGameIds.IndexOf(game)); _statsGameFilter.RefreshShownValue(); ShowSelectedStats(jump: true); } } private void StepStatsGame(int direction) { if (!_statsLoading && _statsGameIds.Count > 1) { int num = _statsGameIds.IndexOf(_statsSelectedGame); int index = Mathf.Clamp(num + direction, 0, _statsGameIds.Count - 1); SelectStatsGameById(_statsGameIds[index]); } } private void ShowSelectedStats(bool jump) { bool flag = !_statsLoading && _statsView != null && _statsView.Scope == _statsScope && _statsView.PlayerId == _statsPlayerId; ((Component)_statsSummary.Root).gameObject.SetActive(flag && _statsSelectedGame < 0 && _statsView.Summary != null); StatsCard statsCard = ((_statsSelectedGame < 0) ? _statsSummary : _statsCards[_statsSelectedGame]); for (int i = 0; i < _statsCards.Length; i++) { if (_statsCards[i] != null) { ((Component)_statsCards[i].Root).gameObject.SetActive(flag && i == _statsSelectedGame); } } if (flag && statsCard != null && statsCard.HasArt) { SetStatsArt(statsCard); } StatsTotals statsTotals = null; if (flag) { statsTotals = _statsView.Summary; if (_statsSelectedGame >= 0 && _statsView.Games != null) { for (int j = 0; j < _statsView.Games.Length; j++) { if (_statsView.Games[j] != null && _statsView.Games[j].GameId == _statsSelectedGame) { statsTotals = _statsView.Games[j].Totals; } } } } ((Component)_statsEmpty).gameObject.SetActive(flag && (statsTotals == null || statsTotals.Played <= 0)); _statsEmpty.text = ((statsTotals == null) ? "No stats available. Use Refresh to try again." : ((_statsSelectedGame < 0) ? "No games recorded yet. Finish a paid casino game to get started." : "You haven't played this game in the selected period yet.")); UpdateStatsNavigation(); RefreshScrollHeights(); if (jump && statsCard != null && ((Component)statsCard.Root).gameObject.activeSelf) { ScrollToItem(_tabs[3], statsCard.Root); } } private void UpdateStatsNavigation() { //IL_010e: 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) if (!((Object)(object)_statsPrevious == (Object)null)) { bool flag = _selectedTab == 3; ((Component)_statsPrevious).gameObject.SetActive(flag); ((Component)_statsOverview).gameObject.SetActive(flag); ((Component)_statsNext).gameObject.SetActive(flag); int num = _statsGameIds.IndexOf(_statsSelectedGame); bool flag2 = !_statsLoading && _statsView != null && _statsView.Scope == _statsScope && _statsView.PlayerId == _statsPlayerId; ((Selectable)_statsPrevious).interactable = flag2 && num > 0; ((Selectable)_statsOverview).interactable = flag2 && _statsSelectedGame >= 0; ((Selectable)_statsNext).interactable = flag2 && num >= 0 && num < _statsGameIds.Count - 1; ((Graphic)_panelFooter).rectTransform.anchoredPosition = new Vector2(flag ? 446f : 26f, -614f); ((Graphic)_panelFooter).rectTransform.sizeDelta = new Vector2(flag ? 268f : 688f, 18f); _panelFooter.text = (flag ? "Scroll for stats / Esc close" : "Tab menu / Esc close / F9 HUD / Scroll for more"); } } private static string StatsText(string value, string fallback, int limit) { if (string.IsNullOrEmpty(value)) { return fallback; } if (value.Length > limit) { return "Unavailable"; } return CompletionText(value, limit, 0); } public void Initialize() { Initialize(null); } public void Initialize(Transform parent) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Expected O, but got Unknown //IL_00f7: Unknown result type (might be due to invalid IL or missing references) if (!_disposed && !((Object)(object)_root != (Object)null)) { _font = LoadFont(); if ((Object)(object)_font == (Object)null) { throw new InvalidOperationException("Chaos Night could not find a runtime UI font."); } _root = new GameObject("Chaos Night UI", new Type[4] { typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster) }); _root.layer = 5; if ((Object)(object)parent != (Object)null) { _root.transform.SetParent(parent, false); } else { Object.DontDestroyOnLoad((Object)(object)_root); } Canvas component = _root.GetComponent(); component.renderMode = (RenderMode)0; component.sortingOrder = 2400; CanvasScaler component2 = _root.GetComponent(); component2.uiScaleMode = (ScaleMode)1; component2.referenceResolution = new Vector2(1280f, 720f); component2.screenMatchMode = (ScreenMatchMode)0; component2.matchWidthOrHeight = 0.5f; BuildHud(_root.transform); BuildPanel(_root.transform); FitPanelToCanvas(); BuildVoteOverlay(_root.transform); BuildCompletionOverlay(_root.transform); _panelContainer.SetActive(false); ((Component)_hudGroup).gameObject.SetActive(false); _voteOverlay.SetActive(false); _completionOverlay.SetActive(false); InputSystem.onAfterUpdate += CaptureHotkeys; _inputSubscribed = true; } } public void Tick() { //IL_00ef: Unknown result type (might be due to invalid IL or missing references) //IL_00f5: Expected O, but got Unknown //IL_00f6: Unknown result type (might be due to invalid IL or missing references) //IL_00fb: 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) if (_disposed || (Object)(object)_root == (Object)null) { return; } if (!HasGameplaySession()) { if (IsOpen) { SetOpen(open: false); } if (((Component)_hudGroup).gameObject.activeSelf) { ((Component)_hudGroup).gameObject.SetActive(false); } ReleaseNativeChallengePresentation(); if (_panelContainer.activeSelf) { _panelContainer.SetActive(false); } if (_voteOverlay.activeSelf) { _voteOverlay.SetActive(false); } ClearCompletionNotices(); if (_panelGroup.alpha != 0f) { _panelGroup.alpha = 0f; } _pendingMenuPress = false; _pendingHudPress = false; _pendingEscapePress = false; _pendingVoteYesPress = false; _pendingVoteNoPress = false; _hadLobby = false; return; } TickCompletionNotices(); RectTransform val = (RectTransform)_root.transform; Rect rect = val.rect; if (!(Math.Abs((double)((Rect)(ref rect)).width - _hudCanvasWidth) > 0.1)) { Rect rect2 = val.rect; if (!(Math.Abs((double)((Rect)(ref rect2)).height - _hudCanvasHeight) > 0.1)) { goto IL_014f; } } RefreshHudVisibility(); FitPanelToCanvas(); goto IL_014f; IL_014f: TickRiskPulse(); if (!_hadLobby) { _hadLobby = true; RefreshHudVisibility(); RefreshVoteVisibility(); } bool flag = Pressed((KeyCode)9); bool flag2 = Pressed((KeyCode)290); bool flag3 = Pressed((KeyCode)27); bool flag4 = Pressed((KeyCode)287); bool flag5 = Pressed((KeyCode)288); bool flag6 = IsEditingText(); if (flag) { SetOpen(!IsOpen); } if (flag2) { ToggleHudLocally(!_hudWanted); } if (IsOpen && flag3) { SetOpen(open: false); } if (!flag6 && !IsEditingText()) { if (flag5 && _state != null && _state.VoteClosed && !_state.VoteActive) { DismissVoteResult(); } else if (flag5) { EmitVote(yes: false); } else if (flag4) { EmitVote(yes: true); } } if (IsOpen) { MaintainCursorLease(); if (_selectionCaptured && (Object)(object)_selectionEventSystem != (Object)(object)EventSystem.current) { RestoreSelection(); _selectionNeedsBind = true; } if (_selectionNeedsBind) { BindExistingEventSystem(); } } if (_panelContainer.activeSelf) { if (_state != null && _state.ReducedMotion) { _panelGroup.alpha = _panelTarget; } else if (_panelGroup.alpha != _panelTarget) { _panelGroup.alpha = Mathf.MoveTowards(_panelGroup.alpha, _panelTarget, Time.unscaledDeltaTime * 8f); } if (!IsOpen && _panelGroup.alpha <= 0f) { _panelContainer.SetActive(false); } } } public void Present(UiState state) { //IL_01aa: Unknown result type (might be due to invalid IL or missing references) //IL_01a2: Unknown result type (might be due to invalid IL or missing references) //IL_0253: Unknown result type (might be due to invalid IL or missing references) //IL_024b: Unknown result type (might be due to invalid IL or missing references) if (_disposed || state == null) { return; } if ((Object)(object)_root == (Object)null) { Initialize(); } _state = state; _presenting = true; try { if (state.SessionReloading || state.PoliceBailVisible) { CloseForSessionReload(); ClearCompletionNotices(); } _hudStatus.text = "CHAOS NIGHT"; string text = state.Round ?? ""; _hudRound.text = ((text.StartsWith("Day ", StringComparison.Ordinal) && text.Length <= 9) ? text : "Next"); _hudModifierSection.Body.text = (state.GameplayActive ? Fallback(state.HudModifiers, "No modifier today.") : "Modifiers appear next casino day."); _hudRiskSection.Body.text = (state.GameplayActive ? Fallback(state.HudRisk, Fallback(state.HudNegatives, "No cash at risk today.")) : "No active cash-risk task."); _hudGoalSection.Body.text = (state.GameplayActive ? Fallback(state.HudGoal, FirstLine(Fallback(state.Contract, "No bonus goal today."))) : "Bonus goal appears next casino day."); _hudModifierSection.SourceText = _hudModifierSection.Body.text; _hudRiskSection.SourceText = _hudRiskSection.Body.text; _hudGoalSection.SourceText = _hudGoalSection.Body.text; ((Graphic)_hudRiskSection.Heading).color = (state.HudRiskActive ? _gold : _paper); RefreshHudLayout(); _round.text = Fallback(state.Round, "Your next chaos night"); _status.text = Fallback(state.Status, "Waiting for the host."); PresentTodayGroup(_modifierCards, state.Modifiers, state.GameplayActive ? "No modifier today." : "Modifiers appear next casino day."); PresentTodayGroup(_riskCards, state.NegativeModifiers, state.GameplayActive ? "No cash at risk today." : "No active cash-risk task."); ((Graphic)_negativeHeading).color = (state.HudRiskActive ? _gold : _paper); PresentTodayGroup(_goalCards, state.Contract, "No bonus goal today."); PresentTodayProgress(_modifierCards, state.ModifierProgress); PresentTodayProgress(_riskCards, state.RiskProgress); PresentGoalProgress(_goalCards, state.GoalProgress); PresentProgress(_hudModifierSection.Progress, SingleProgress(state.ModifierProgress)); PresentProgress(_hudRiskSection.Progress, SingleProgress(state.RiskProgress)); PresentProgress(_hudGoalSection.Progress, state.GoalProgress); RefreshHudLayout(); _riskLimits.text = state.RiskLimits ?? ""; ((Component)_riskLimits).gameObject.SetActive(!string.IsNullOrEmpty(state.RiskLimits)); PresentTodayGroup(_perkCards, state.Perks, "No crew perks yet. Earn a perk choice by meeting the day's target."); _summary.text = Fallback(state.Summary, "Play a day to begin your crew's story."); _actionHint.text = Fallback(state.ActionHint, "Modifiers and goals appear when the next casino day starts."); _rewardSummary.text = Fallback(state.RewardSummary, "No Chaos payments yet."); PresentCashOverview(state.CashSummary); PresentProgress(_cashTargetBar, state.CashTargetProgress); PresentProgress(_cashCapBar, state.BonusCapProgress); FitPanelToCanvas(); _hostHint.text = Fallback(state.SettingsHint, state.IsHost ? "Rule changes apply next casino day." : "Host controls only. Rule changes apply next casino day."); _rerollModifierHint.text = RerollHint(state.RerollModifierHint, state.IsHost, state.CanRerollModifier, "Spend Wildcard to replace one modifier.", "Needs an unused Wildcard charge. Use before betting."); _rerollContractHint.text = RerollHint(state.RerollContractHint, state.IsHost, state.CanRerollContract, "Spend Second Opinion to replace the bonus goal.", "Needs an unused Second Opinion charge. Use before betting."); _rerollPerksHint.text = RerollHint(state.RerollPerksHint, state.IsHost, state.CanRerollPerks, "Spend Reroll to replace these perk choices.", "Needs an unused Reroll charge and open perk choices."); _enabled.isOn = state.Enabled; _closingTime.isOn = state.ClosingTimeEnabled; _contracts.isOn = state.ContractsEnabled; _negatives.isOn = state.NegativesEnabled; _timedNegatives.isOn = state.TimedNegativesEnabled; _reducedMotion.isOn = state.ReducedMotion; _intensity.value = Mathf.Clamp(state.Intensity, 1, 10); _jackpot.value = Mathf.Clamp(state.JackpotPercent, 0f, 20f); _bonus.value = Mathf.Clamp(state.BonusCap, 5f, 50f); _penaltySeverity.value = Mathf.Clamp(state.PenaltySeverity, 1, 3); _riskChance.value = Mathf.Clamp(state.RiskDayPercent, 0f, 100f); _riskChanceLabel.text = "Days with cash-risk tasks " + state.RiskDayPercent.ToString("0.#") + "%"; _penaltySeverityLabel.text = PenaltySeverityCaption(state.PenaltySeverity); _intensityLabel.text = "Chaos intensity " + state.Intensity + " / 10"; _jackpotLabel.text = "Jackpot chance " + state.JackpotPercent.ToString("0.#") + "%"; _bonusLabel.text = "Daily bonus cash limit " + state.BonusCap.ToString("0.#") + "% of original target"; _presetLabel.text = "Preset: " + Fallback(state.Preset, "Custom"); ((Selectable)_enabled).interactable = state.IsHost; ((Selectable)_closingTime).interactable = state.IsHost; ((Selectable)_contracts).interactable = state.IsHost; ((Selectable)_negatives).interactable = state.IsHost; ((Selectable)_timedNegatives).interactable = state.IsHost; ((Selectable)_intensity).interactable = state.IsHost; ((Selectable)_jackpot).interactable = state.IsHost; ((Selectable)_bonus).interactable = state.IsHost; ((Selectable)_penaltySeverity).interactable = state.IsHost; ((Selectable)_riskChance).interactable = state.IsHost; ((Selectable)_reducedMotion).interactable = true; ((Selectable)_rerollModifier).interactable = state.IsHost && state.CanRerollModifier; ((Selectable)_rerollContract).interactable = state.IsHost && state.CanRerollContract; ((Selectable)_rerollPerks).interactable = state.IsHost && state.CanRerollPerks; ((Selectable)_requestRestartVote).interactable = state.IsHost && state.CanRequestRestartVote; ((Selectable)_cancelRestartVote).interactable = state.IsHost && state.CanCancelVote; ((Selectable)_applyFreshRun).interactable = state.IsHost && state.CanStartFreshRun && _freshRun != null; ((Selectable)_repairAllBodies).interactable = state.IsHost && state.CanRepairAllBodies; _bodyRepairHint.text = RerollHint(state.BodyRepairHint, state.IsHost, state.CanRepairAllBodies, "Ready. Free repair for everyone.", "Wait for everyone to finish loading."); ((Selectable)_freshDay).interactable = state.IsHost; ((Selectable)_freshMoney).interactable = state.IsHost; _restartVoteHint.text = Fallback(state.RestartVoteHint, state.IsHost ? "Whole-party vote. More than half must say yes." : "The host can start a restart vote."); _cancelVoteHint.text = ((!state.IsHost) ? "Only the host can cancel the vote." : (state.CanCancelVote ? "End this vote and keep playing." : "No vote in progress.")); _freshRunHint.text = Fallback(state.FreshRunHint, state.IsHost ? "Choose a day and shared cash. Apply when everyone is ready." : "Only the host can replace the run."); int num = ((state.FreshRunMinDay <= 0) ? 1 : state.FreshRunMinDay); int num2 = ((state.FreshRunMaxDay >= num) ? state.FreshRunMaxDay : 999); _freshDayLabel.text = "Starting day (" + num + " to " + num2 + ")"; if (!_freshFieldsInitialized && (!string.IsNullOrEmpty(state.FreshRunDefaultDay) || !string.IsNullOrEmpty(state.FreshRunDefaultMoney))) { _freshDay.text = Fallback(state.FreshRunDefaultDay, num.ToString()); _freshMoney.text = state.FreshRunDefaultMoney ?? ""; _freshFieldsInitialized = true; } RefreshVotePresentation(); for (int i = 0; i < _presets.Length; i++) { ((Selectable)_presets[i]).interactable = state.IsHost; } int num3 = ((state.Choices != null) ? Math.Min(state.Choices.Length, _choices.Length) : 0); ((Component)_choiceHeading).gameObject.SetActive(num3 > 0); ((Component)_choiceHint).gameObject.SetActive(num3 > 0); _choiceSection.Root.SetActive(num3 > 0); _choiceHint.text = (state.IsHost ? "Choose one offer for the whole crew." : "The host chooses one offer for the whole crew."); for (int j = 0; j < _choices.Length; j++) { ((Component)_choices[j]).gameObject.SetActive(j < num3); ((Selectable)_choices[j]).interactable = state.IsHost; if (j < num3) { PresentChoice(j, state.Choices[j]); } } RefreshScrollHeights(); RefreshHudVisibility(); if (state.ReducedMotion) { _panelGroup.alpha = _panelTarget; if (!IsOpen) { _panelContainer.SetActive(false); } } } finally { _presenting = false; } } public void ShowCompletion(string title, string body, bool penalty) { ShowCompletion(title, body, penalty, penalty ? "SHARED CASH PENALTY" : "BONUS PAID"); } public void ShowCompletion(string title, string body, bool penalty, string status) { ShowCompletion(title, body, penalty, status, gunPurchase: false); } public void ShowGunPurchaseCompletion(string title, string body) { ShowCompletion(title, body, penalty: false, "PURCHASE CONFIRMED", gunPurchase: true); } private void ShowCompletion(string title, string body, bool penalty, string status, bool gunPurchase) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0029: 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) if (_disposed || !HasGameplaySession()) { return; } if ((Object)(object)_root == (Object)null) { Initialize(); } Scene activeScene = SceneManager.GetActiveScene(); int num = SceneHandle.op_Implicit(((Scene)(ref activeScene)).handle); if (_completionScene != num) { ClearCompletionNotices(); } _completionScene = num; CompletionMessage completionMessage = new CompletionMessage(); completionMessage.Status = CompletionText(status, 80, 0); completionMessage.Title = CompletionText(title, 160, 0); completionMessage.Body = CompletionText(body, 320, 3); completionMessage.Penalty = penalty; completionMessage.GunPurchase = gunPurchase; CompletionMessage completionMessage2 = completionMessage; if (completionMessage2.Title.Length == 0) { completionMessage2.Title = (penalty ? "Task missed" : "Bonus paid"); } if (gunPurchase) { CoalescePurchaseNotices(_completionQueue); } if (ReplaceCompletion(_completionCurrent, completionMessage2)) { DisplayCompletion(completionMessage2); return; } if (_completionQueue.Count >= 8) { _completionQueue.Dequeue(); } _completionQueue.Enqueue(completionMessage2); } private static void CoalescePurchaseNotices(Queue notices) { int count = notices.Count; for (int i = 0; i < count; i++) { CompletionMessage completionMessage = notices.Dequeue(); if (!completionMessage.GunPurchase) { notices.Enqueue(completionMessage); } } } private static bool ReplaceCompletion(CompletionMessage current, CompletionMessage next) { if (current != null) { if (next.GunPurchase) { return current.GunPurchase; } return false; } return true; } private static float CompletionExpiry(float now, CompletionMessage notice) { return now + (notice.GunPurchase ? 3f : 6f); } private static string CompletionText(string value, int limit, int lineBreaks) { if (string.IsNullOrEmpty(value)) { return ""; } StringBuilder stringBuilder = new StringBuilder(Math.Min(limit, value.Length)); int num = 0; for (int i = 0; i < value.Length; i++) { if (stringBuilder.Length >= limit) { break; } char c = value[i]; switch (c) { case '\n': stringBuilder.Append((num++ < lineBreaks) ? '\n' : ' '); continue; case '\t': stringBuilder.Append(' '); continue; case '\r': continue; } if (char.IsControl(c) || char.GetUnicodeCategory(c) == UnicodeCategory.Format) { continue; } if (char.IsHighSurrogate(c)) { if (i + 1 < value.Length && char.IsLowSurrogate(value[i + 1]) && stringBuilder.Length + 2 <= limit) { stringBuilder.Append(c); stringBuilder.Append(value[++i]); } } else if (!char.IsLowSurrogate(c)) { stringBuilder.Append(c); } } return stringBuilder.ToString().Trim(); } private void TickCompletionNotices() { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) if (_completionScene >= 0) { SceneHandle val = SceneHandle.op_Implicit(_completionScene); Scene activeScene = SceneManager.GetActiveScene(); if (val != ((Scene)(ref activeScene)).handle) { ClearCompletionNotices(); return; } } if (_completionCurrent != null && !(Time.unscaledTime < _completionExpires)) { _completionCurrent = null; if (_completionQueue.Count > 0) { DisplayCompletion(_completionQueue.Dequeue()); } else { _completionOverlay.SetActive(false); } } } private void DisplayCompletion(CompletionMessage notice) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0048: 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_00b9: 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_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0126: Unknown result type (might be due to invalid IL or missing references) _completionCurrent = notice; Color color = (Color)(notice.Penalty ? new Color(1f, 0.46f, 0.42f, 1f) : _gold); _completionStatus.text = notice.Status; ((Graphic)_completionStatus).color = color; ((Graphic)_completionAccent).color = color; _completionTitle.text = notice.Title; _completionBody.text = notice.Body; float num = CompletionHeight(_completionTitle, 488f, 24f); float num2 = CompletionHeight(_completionBody, 488f, 21f); ((Graphic)_completionTitle).rectTransform.sizeDelta = new Vector2(488f, num); ((Graphic)_completionBody).rectTransform.anchoredPosition = new Vector2(16f, 0f - (35f + num + 7f)); ((Graphic)_completionBody).rectTransform.sizeDelta = new Vector2(488f, num2); _completionBox.sizeDelta = new Vector2(520f, 35f + num + 7f + num2 + 14f); _completionExpires = CompletionExpiry(Time.unscaledTime, notice); _completionOverlay.SetActive(true); } private static float CompletionHeight(Text text, float width, float minimum) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) TextGenerationSettings generationSettings = text.GetGenerationSettings(new Vector2(width, 0f)); return Mathf.Max(minimum, text.cachedTextGeneratorForLayout.GetPreferredHeight(text.text, generationSettings) / text.pixelsPerUnit + 3f); } private void ClearCompletionNotices() { _completionQueue.Clear(); _completionCurrent = null; _completionExpires = 0f; _completionScene = -1; if ((Object)(object)_completionOverlay != (Object)null && _completionOverlay.activeSelf) { _completionOverlay.SetActive(false); } } public void Dispose() { if (!_disposed) { ClearCompletionNotices(); ReleaseNativeChallengePresentation(); _nativeUpgradeBounds = null; RestoreCursor(); RestoreSelection(); IsOpen = false; if (_inputSubscribed) { InputSystem.onAfterUpdate -= CaptureHotkeys; _inputSubscribed = false; } if ((Object)(object)_root != (Object)null) { Object.Destroy((Object)(object)_root); } _root = null; _statsRequest = null; _statsNativeArt = null; _statsArtSource = null; _statsView = null; _statsPlayers.Clear(); _disposed = true; } } public void CloseForSessionReload() { if (!_disposed && !((Object)(object)_root == (Object)null)) { if (IsOpen) { SetOpen(open: false); } _panelTarget = 0f; _panelGroup.alpha = 0f; _panelContainer.SetActive(false); } } private void SetOpen(bool open) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0081: Unknown result type (might be due to invalid IL or missing references) if ((open && (!HasGameplaySession() || (_state != null && _state.SessionReloading))) || IsOpen == open) { return; } IsOpen = open; RefreshHudVisibility(); RefreshVotePresentation(); _panelTarget = (open ? 1f : 0f); _panelGroup.interactable = open; _panelGroup.blocksRaycasts = open; if (open) { SelectTab(0); _selectionNeedsBind = true; _savedCursorLock = Cursor.lockState; _savedCursorVisible = Cursor.visible; _cursorCaptured = true; MaintainCursorLease(); _panelContainer.SetActive(true); BindExistingEventSystem(); RefreshScrollHeights(); if (_state != null && _state.ReducedMotion) { _panelGroup.alpha = 1f; } } else { _closedFrame = Time.frameCount; RestoreCursor(); RestoreSelection(); if (_state != null && _state.ReducedMotion) { _panelGroup.alpha = 0f; _panelContainer.SetActive(false); } } } private void RestoreCursor() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) bool nativeVisible; bool flag = RestoreNativeCursorImage(out nativeVisible); if (_cursorCaptured) { Cursor.lockState = (CursorLockMode)((!flag) ? ((int)_savedCursorLock) : ((!nativeVisible) ? 1 : 2)); Cursor.visible = !flag && _savedCursorVisible; _cursorCaptured = false; } } public void MaintainCursorLease() { //IL_0118: Unknown result type (might be due to invalid IL or missing references) if (_disposed || !IsOpen || !_cursorCaptured) { return; } UICursorSimple val = (UICursorSimple)((NativeCursorInstance == null) ? null : /*isinst with value type is only supported in some contexts*/); Image val2 = (Image)(((Object)(object)val == (Object)null || NativeCursorImage == null) ? null : /*isinst with value type is only supported in some contexts*/); if (!object.ReferenceEquals(val, _nativeCursor) || !object.ReferenceEquals(val2, _nativeCursorImage)) { RestoreNativeCursorImage(out var _); _nativeCursor = val; _nativeCursorImage = val2; _nativeVisibilityCaptured = (Object)(object)val != (Object)null && NativeCursorVisibility != null; if (_nativeVisibilityCaptured) { _savedNativeCursorVisible = (bool)NativeCursorVisibility.GetValue(val); } if ((Object)(object)_nativeCursorImage != (Object)null) { _savedNativeCursorImageEnabled = ((Behaviour)_nativeCursorImage).enabled; } } if ((Object)(object)_nativeCursorImage != (Object)null && ((Behaviour)_nativeCursorImage).enabled) { ((Behaviour)_nativeCursorImage).enabled = false; } if ((int)Cursor.lockState != 0) { Cursor.lockState = (CursorLockMode)0; } if (!Cursor.visible) { Cursor.visible = true; } } private bool RestoreNativeCursorImage(out bool nativeVisible) { nativeVisible = false; bool flag = false; if ((Object)(object)_nativeCursor != (Object)null && _nativeVisibilityCaptured) { nativeVisible = (bool)NativeCursorVisibility.GetValue(_nativeCursor); flag = nativeVisible != _savedNativeCursorVisible; } if ((Object)(object)_nativeCursorImage != (Object)null) { ((Behaviour)_nativeCursorImage).enabled = ((!flag) ? _savedNativeCursorImageEnabled : (nativeVisible && (NativeUsesUiCursor == null || (bool)NativeUsesUiCursor.GetValue(_nativeCursor)))); } _nativeCursorImage = null; _nativeCursor = null; _savedNativeCursorImageEnabled = false; _savedNativeCursorVisible = false; _nativeVisibilityCaptured = false; return flag; } private void BindExistingEventSystem() { if (_selectionNeedsBind && !((Object)(object)EventSystem.current == (Object)null)) { _selectionEventSystem = EventSystem.current; _previousSelection = _selectionEventSystem.currentSelectedGameObject; _selectionEventSystem.SetSelectedGameObject(((Component)_close).gameObject); _selectionCaptured = true; _selectionNeedsBind = false; } } private void RestoreSelection() { if (_selectionCaptured && (Object)(object)_selectionEventSystem != (Object)null) { _selectionEventSystem.SetSelectedGameObject(_previousSelection); } _previousSelection = null; _selectionEventSystem = null; _selectionNeedsBind = false; _selectionCaptured = false; } private bool Pressed(KeyCode key) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001d: Invalid comparison between Unknown and I4 //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Invalid comparison between Unknown and I4 //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Invalid comparison between Unknown and I4 //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Invalid comparison between Unknown and I4 bool flag = false; if ((int)key == 9) { flag = _pendingMenuPress; _pendingMenuPress = false; } else if ((int)key == 290) { flag = _pendingHudPress; _pendingHudPress = false; } else if ((int)key == 27) { flag = _pendingEscapePress; _pendingEscapePress = false; } else if ((int)key == 287) { flag = _pendingVoteYesPress; _pendingVoteYesPress = false; } else if ((int)key == 288) { flag = _pendingVoteNoPress; _pendingVoteNoPress = false; } if (!_legacyInputAvailable) { return flag; } try { return Input.GetKeyDown(key) || flag; } catch (InvalidOperationException) { _legacyInputAvailable = false; return flag; } } private void CaptureHotkeys() { //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: 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_0018: Invalid comparison between Unknown and I4 //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (_disposed || !HasGameplaySession()) { return; } InputUpdateType currentUpdateType = InputState.currentUpdateType; if ((int)currentUpdateType == 4 || (int)currentUpdateType == 8) { return; } Keyboard current = Keyboard.current; if (current != null) { uint updateCount = InputState.updateCount; if (((ButtonControl)current.tabKey).wasPressedThisFrame && _lastMenuUpdate != updateCount) { _lastMenuUpdate = updateCount; _pendingMenuPress = true; } if (((ButtonControl)current.f9Key).wasPressedThisFrame && _lastHudUpdate != updateCount) { _lastHudUpdate = updateCount; _pendingHudPress = true; } if (((ButtonControl)current.escapeKey).wasPressedThisFrame && _lastEscapeUpdate != updateCount) { _lastEscapeUpdate = updateCount; _pendingEscapePress = true; } if (((ButtonControl)current.f6Key).wasPressedThisFrame && _lastVoteYesUpdate != updateCount) { _lastVoteYesUpdate = updateCount; _pendingVoteYesPress = true; } if (((ButtonControl)current.f7Key).wasPressedThisFrame && _lastVoteNoUpdate != updateCount) { _lastVoteNoUpdate = updateCount; _pendingVoteNoPress = true; } } } private void RefreshHudVisibility() { if (!((Object)(object)_hudGroup == (Object)null)) { RefreshHudLayout(); ((Component)_hudGroup).gameObject.SetActive(HasGameplaySession() && !IsOpen && _hudWanted && _state != null && _hudHasSafePlacement && (_state.Enabled || _state.GameplayActive)); } } public void BindNativeChallengePresentation(Transform exactChallengeRoot) { } public void ReleaseNativeChallengePresentation() { } public void BindNativeUpgradePresentation(Transform exactEntryParent) { if (!_disposed) { _nativeUpgradeBounds = exactEntryParent; RefreshHudVisibility(); } } public void MaintainNativeChallengePresentation() { //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_0082: Unknown result type (might be due to invalid IL or missing references) if (_disposed || (Object)(object)_root == (Object)null || (Object)(object)_hudBox == (Object)null) { return; } RectTransform val = (RectTransform)_root.transform; double num = NativeUpgradesBottom(val); if (!(Math.Abs(num - _hudNativeBottom) > 0.1)) { Rect rect = val.rect; if (!(Math.Abs((double)((Rect)(ref rect)).width - _hudCanvasWidth) > 0.1)) { Rect rect2 = val.rect; if (!(Math.Abs((double)((Rect)(ref rect2)).height - _hudCanvasHeight) > 0.1)) { return; } } } RefreshHudVisibility(); } private double NativeUpgradesBottom(RectTransform canvasRect) { //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_0070: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0107: Unknown result type (might be due to invalid IL or missing references) //IL_010c: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_012e: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Unknown result type (might be due to invalid IL or missing references) //IL_011c: Unknown result type (might be due to invalid IL or missing references) //IL_0121: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_nativeUpgradeBounds == (Object)null || !((Component)_nativeUpgradeBounds).gameObject.activeInHierarchy) { return -1.0; } Canvas componentInParent = ((Component)_nativeUpgradeBounds).GetComponentInParent(); if ((Object)(object)componentInParent == (Object)null || (int)componentInParent.rootCanvas.renderMode != 0) { return -1.0; } double num = -1.0; if (_nativeUpgradeBounds.childCount > 64) { Rect rect = canvasRect.rect; return ((Rect)(ref rect)).height; } int num2 = Math.Min(64, _nativeUpgradeBounds.childCount); Vector2 val3 = default(Vector2); for (int i = 0; i < num2; i++) { Transform child = _nativeUpgradeBounds.GetChild(i); RectTransform val = (RectTransform)(object)((child is RectTransform) ? child : null); if ((Object)(object)val == (Object)null || !((Component)val).gameObject.activeInHierarchy) { continue; } Rect rect2 = val.rect; if (((Rect)(ref rect2)).height <= 0f) { continue; } val.GetWorldCorners(_nativeHudCorners); for (int j = 0; j < 4; j++) { Vector2 val2 = RectTransformUtility.WorldToScreenPoint((Camera)null, _nativeHudCorners[j]); if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(canvasRect, val2, (Camera)null, ref val3)) { Rect rect3 = canvasRect.rect; return ((Rect)(ref rect3)).height; } double val4 = num; Rect rect4 = canvasRect.rect; num = Math.Max(val4, ((Rect)(ref rect4)).yMax - val3.y); } } return num; } private void UpdateOwnHudPlacement() { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown //IL_0030: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Unknown result type (might be due to invalid IL or missing references) //IL_004a: Unknown result type (might be due to invalid IL or missing references) //IL_00ad: Unknown result type (might be due to invalid IL or missing references) //IL_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: 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) //IL_00d1: 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_0110: Unknown result type (might be due to invalid IL or missing references) //IL_011a: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_root == (Object)null) && !((Object)(object)_hudBox == (Object)null)) { RectTransform val = (RectTransform)_root.transform; Rect rect = val.rect; _hudCanvasWidth = ((Rect)(ref rect)).width; Rect rect2 = val.rect; _hudCanvasHeight = ((Rect)(ref rect2)).height; _hudNativeBottom = NativeUpgradesBottom(val); ChaosPresentation.HudBounds compactHudBounds = ChaosPresentation.GetCompactHudBounds(_hudCanvasWidth, _hudCanvasHeight, _hudNativeBottom); _hudHasSafePlacement = compactHudBounds.Visible; _hudMaximumHeight = compactHudBounds.MaximumHeight; RectTransform hudBox = _hudBox; Vector2 anchorMin = (_hudBox.anchorMax = new Vector2(0f, 1f)); hudBox.anchorMin = anchorMin; _hudBox.pivot = new Vector2(0f, 1f); _hudBox.anchoredPosition = new Vector2((float)compactHudBounds.X, 0f - (float)compactHudBounds.Y); _hudBox.sizeDelta = new Vector2((float)compactHudBounds.Width, _hudBox.sizeDelta.y); if ((Object)(object)_status != (Object)null && _state != null) { _status.text = Fallback(_state.Status, "Waiting for the host.") + (compactHudBounds.Visible ? "" : "\nHUD doesn't fit below Upgrades. Tab shows every goal."); } } } private static bool HasGameplaySession() { if (!NetworkServer.active) { return NetworkClient.active; } return true; } private void BuildHud(Transform parent) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_016f: Unknown result type (might be due to invalid IL or missing references) _hudBox = Rect(parent, "Crew HUD", 14f, 219f, 214f, 260f); Image(_hudBox, new Color(0.035f, 0.043f, 0.055f, 1f), raycast: false); _hudGroup = ((Component)_hudBox).gameObject.AddComponent(); _hudGroup.interactable = false; _hudGroup.blocksRaycasts = false; _hudStatus = Label((Transform)(object)_hudBox, "Title", 8f, 7f, 144f, 18f, "CHAOS NIGHT", 13, _gold); _hudStatus.fontStyle = (FontStyle)1; _hudRound = Label((Transform)(object)_hudBox, "Round", 158f, 8f, 48f, 17f, "Day 1", 10, _paper); _hudRound.alignment = (TextAnchor)5; _hudModifierSection = BuildHudSection((Transform)(object)_hudBox, "Modifiers"); _hudRiskSection = BuildHudSection((Transform)(object)_hudBox, "Cash at risk"); _hudGoalSection = BuildHudSection((Transform)(object)_hudBox, "Bonus goal"); _hudFooter = Label((Transform)(object)_hudBox, "HUD keys", 8f, 244f, 198f, 16f, "Tab details / F9 hide", 10, _muted); _hudBody = _hudModifierSection.Body; RefreshHudLayout(); } private HudSection BuildHudSection(Transform parent, string caption) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) RectTransform val = Rect(parent, caption, 8f, 29f, 198f, 48f); Image surface = Image(val, new Color(0.085f, 0.1f, 0.12f, 1f), raycast: false); Text val2 = Label((Transform)(object)val, "Heading", 8f, 4f, 182f, 17f, caption, 12, _paper); val2.fontStyle = (FontStyle)1; Text val3 = Label((Transform)(object)val, "Progress", 8f, 22f, 182f, 20f, "Revealed next casino day", 11, _paper); val3.supportRichText = false; HudSection hudSection = new HudSection(); hudSection.Root = val; hudSection.Surface = surface; hudSection.Heading = val2; hudSection.Body = val3; hudSection.SourceText = "Revealed next casino day"; hudSection.Progress = BuildProgress((Transform)(object)val, "Goal progress", 182f, caption == "Cash at risk", caption: false); return hudSection; } private void RefreshHudLayout() { //IL_003a: 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_0085: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Unknown result type (might be due to invalid IL or missing references) //IL_022c: Unknown result type (might be due to invalid IL or missing references) //IL_024d: Unknown result type (might be due to invalid IL or missing references) //IL_0272: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)_hudBox == (Object)null) && _hudModifierSection != null && !((Object)(object)_hudFooter == (Object)null)) { UpdateOwnHudPlacement(); if (_hudHasSafePlacement) { float x = _hudBox.sizeDelta.x; float bodyWidth = x - 32f; ((Graphic)_hudStatus).rectTransform.sizeDelta = new Vector2(x - 68f, 18f); ((Graphic)_hudRound).rectTransform.anchoredPosition = new Vector2(x - 56f, -8f); ((Graphic)_hudRound).rectTransform.sizeDelta = new Vector2(48f, 17f); float num = Math.Max(0f, (float)_hudMaximumHeight - 169f); float num2 = HudBodyHeight(_hudModifierSection, bodyWidth, 160); float num3 = HudBodyHeight(_hudRiskSection, bodyWidth, 160); float num4 = HudBodyHeight(_hudGoalSection, bodyWidth, 160); float num5 = Mathf.Min(36f, num / 3f); float num6 = Math.Max(0f, num - num5 * 3f); float num7 = Math.Max(0f, num2 - num5) + Math.Max(0f, num3 - num5) + Math.Max(0f, num4 - num5); float y = 29f; y = PlaceHudSection(_hudModifierSection, y, x, Math.Min(num2, num5 + ((num7 > 0f) ? (num6 * Math.Max(0f, num2 - num5) / num7) : 0f))); y = PlaceHudSection(_hudRiskSection, y, x, Math.Min(num3, num5 + ((num7 > 0f) ? (num6 * Math.Max(0f, num3 - num5) / num7) : 0f))); y = PlaceHudSection(_hudGoalSection, y, x, Math.Min(num4, num5 + ((num7 > 0f) ? (num6 * Math.Max(0f, num4 - num5) / num7) : 0f))); ((Graphic)_hudFooter).rectTransform.anchoredPosition = new Vector2(8f, 0f - y); ((Graphic)_hudFooter).rectTransform.sizeDelta = new Vector2(x - 16f, 16f); _hudBox.sizeDelta = new Vector2(x, Math.Min((float)_hudMaximumHeight, y + 22f)); TickRiskPulse(force: true); } } } private static float HudBodyHeight(HudSection section, float bodyWidth, int limit) { section.Body.text = ChaosPresentation.CompactHudLines(section.SourceText, limit, 2); return CompletionHeight(section.Body, bodyWidth, 17f); } private static float PlaceHudSection(HudSection section, float y, float width, float allocation) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_008c: Unknown result type (might be due to invalid IL or missing references) //IL_0114: Unknown result type (might be due to invalid IL or missing references) //IL_0125: 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) float num = width - 32f; int num2 = 16; int num3 = 160; int limit = 16; while (num2 <= num3) { int num4 = (num2 + num3) / 2; if (HudBodyHeight(section, num, num4) <= allocation) { limit = num4; num2 = num4 + 1; } else { num3 = num4 - 1; } } float num5 = Math.Min(allocation, HudBodyHeight(section, num, limit)); ((Graphic)section.Body).rectTransform.sizeDelta = new Vector2(num, num5); ((Graphic)section.Heading).rectTransform.sizeDelta = new Vector2(num, 17f); section.Root.anchoredPosition = new Vector2(8f, 0f - y); float num6 = ((section.Progress != null && ((Component)section.Progress.Root).gameObject.activeSelf) ? 8f : 0f); if (num6 > 0f) { section.Progress.Root.anchoredPosition = new Vector2(8f, 0f - (24f + num5)); LayoutProgress(section.Progress, num); } section.Root.sizeDelta = new Vector2(width - 16f, 26f + num5 + num6); return y + section.Root.sizeDelta.y + 4f; } private void TickRiskPulse() { TickRiskPulse(force: false); } private void TickRiskPulse(bool force) { //IL_00fd: Unknown result type (might be due to invalid IL or missing references) //IL_0102: Unknown result type (might be due to invalid IL or missing references) //IL_0115: Unknown result type (might be due to invalid IL or missing references) //IL_00cd: Unknown result type (might be due to invalid IL or missing references) //IL_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: 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) if (_hudRiskSection != null && (force || ((Component)_hudGroup).gameObject.activeInHierarchy) && (force || !(Time.unscaledTime < _nextRiskPulse))) { _nextRiskPulse = Time.unscaledTime + 0.1f; bool flag = _state != null && _state.GameplayActive && _state.HudRiskActive; Color val = default(Color); ((Color)(ref val))..ctor(0.085f, 0.1f, 0.12f, 1f); if (flag) { float num = (_state.ReducedMotion ? 0f : ((1f - Mathf.Cos(Time.unscaledTime * (float)Math.PI * 0.5f)) * 0.5f)); val = Color.Lerp(new Color(0.19f, 0.105f, 0.065f, 1f), new Color(0.24f, 0.14f, 0.085f, 1f), num); } if (((Graphic)_hudRiskSection.Surface).color != val) { ((Graphic)_hudRiskSection.Surface).color = val; } } } private void BuildCompletionOverlay(Transform parent) { //IL_0035: Unknown result type (might be due to invalid IL or missing references) //IL_004f: 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_0083: Unknown result type (might be due to invalid IL or missing references) //IL_00ce: Unknown result type (might be due to invalid IL or missing references) //IL_0100: Unknown result type (might be due to invalid IL or missing references) //IL_0139: Unknown result type (might be due to invalid IL or missing references) //IL_0171: Unknown result type (might be due to invalid IL or missing references) //IL_01a9: Unknown result type (might be due to invalid IL or missing references) _completionBox = Rect(parent, "Completion notice", 0f, 0f, 520f, 112f); _completionBox.anchorMin = new Vector2(0.5f, 0f); _completionBox.anchorMax = new Vector2(0.5f, 0f); _completionBox.pivot = new Vector2(0.5f, 0f); _completionBox.anchoredPosition = new Vector2(0f, 64f); _completionOverlay = ((Component)_completionBox).gameObject; _completionGroup = _completionOverlay.AddComponent(); _completionGroup.blocksRaycasts = false; _completionGroup.interactable = false; Image(_completionBox, _ink, raycast: false); _completionAccent = Image(Rect((Transform)(object)_completionBox, "Notice accent", 0f, 0f, 520f, 3f), _gold, raycast: false); _completionStatus = Label((Transform)(object)_completionBox, "Notice status", 16f, 12f, 488f, 18f, "BONUS PAID", 12, _gold); _completionTitle = Label((Transform)(object)_completionBox, "Notice title", 16f, 35f, 488f, 24f, "Bonus paid", 18, _paper); _completionBody = Label((Transform)(object)_completionBox, "Notice body", 16f, 66f, 488f, 30f, "", 15, _paper); } private void BuildVoteOverlay(Transform parent) { //IL_002b: 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_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0065: Unknown result type (might be due to invalid IL or missing references) //IL_0087: Unknown result type (might be due to invalid IL or missing references) //IL_00b8: Unknown result type (might be due to invalid IL or missing references) //IL_00ec: Unknown result type (might be due to invalid IL or missing references) //IL_0120: Unknown result type (might be due to invalid IL or missing references) //IL_0158: Unknown result type (might be due to invalid IL or missing references) //IL_0190: Unknown result type (might be due to invalid IL or missing references) //IL_01ce: Unknown result type (might be due to invalid IL or missing references) //IL_01d8: Expected O, but got Unknown //IL_020c: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Expected O, but got Unknown //IL_024a: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Expected O, but got Unknown _voteBox = Rect(parent, "Restart vote", 0f, 0f, 240f, 196f); _voteBox.anchorMin = Vector2.one; _voteBox.anchorMax = Vector2.one; _voteBox.pivot = Vector2.one; _voteBox.anchoredPosition = new Vector2(-20f, -220f); _voteOverlay = ((Component)_voteBox).gameObject; Image(_voteBox, _ink, raycast: false); Image(Rect((Transform)(object)_voteBox, "Vote rule", 0f, 0f, 240f, 2f), _gold, raycast: false); Label((Transform)(object)_voteBox, "Vote heading", 12f, 10f, 216f, 24f, "Restart vote", 18, _gold); _voteStatus = Label((Transform)(object)_voteBox, "Vote status", 12f, 41f, 216f, 39f, "Restart today's casino day?", 14, _paper); _voteTally = Label((Transform)(object)_voteBox, "Vote tally", 12f, 86f, 216f, 34f, "Waiting for the party", 14, _paper); _voteHint = Label((Transform)(object)_voteBox, "Vote instructions", 12f, 126f, 216f, 34f, "F6 yes / F7 no. More than half must vote yes.", 12, _muted); _voteYes = CreateButton((Transform)(object)_voteBox, "Vote yes", 12f, 163f, 102f, 32f, "Yes · F6", 14, (UnityAction)delegate { EmitVote(yes: true); }); _voteNo = CreateButton((Transform)(object)_voteBox, "Vote no", 126f, 163f, 102f, 32f, "No · F7", 14, (UnityAction)delegate { EmitVote(yes: false); }); _dismissVote = CreateButton((Transform)(object)_voteBox, "Dismiss vote result", 12f, 163f, 216f, 32f, "Dismiss · F7", 14, new UnityAction(DismissVoteResult)); } private void RefreshVotePresentation() { //IL_026c: Unknown result type (might be due to invalid IL or missing references) //IL_0279: Unknown result type (might be due to invalid IL or missing references) //IL_028e: Unknown result type (might be due to invalid IL or missing references) //IL_029b: Unknown result type (might be due to invalid IL or missing references) //IL_02f3: Unknown result type (might be due to invalid IL or missing references) //IL_02bf: Unknown result type (might be due to invalid IL or missing references) //IL_02cc: Unknown result type (might be due to invalid IL or missing references) if (_state != null && !((Object)(object)_voteOverlay == (Object)null)) { _voteStatus.text = Fallback(_state.VoteStatus, _state.VoteActive ? "Restart today's casino day?" : "The restart vote has ended."); _voteTally.text = ((_state.VoteRequired > 0) ? ("Yes: " + _state.VoteYes + " / " + _state.VoteRequired + " needed\nNo: " + _state.VoteNo + " · Party: " + _state.VotePlayers) : "Waiting for the party"); string text = Fallback(_state.VoteHint, (!_state.VoteActive) ? "The host can request another vote when it is available." : (_state.CanVote ? "More than half must vote yes." : "Your vote is recorded, or you are observing this vote.")); bool flag = _state.VoteClosed && !_state.VoteActive; _voteHint.text = (flag ? ("F7 dismiss\n" + text) : ((_state.VoteActive && _state.CanVote) ? ("F6 yes / F7 no\n" + text) : text)); bool interactable = _state.VoteActive && _state.CanVote; ((Selectable)_voteYes).interactable = interactable; ((Selectable)_voteNo).interactable = interactable; bool flag2 = _state.VoteActive && IsOpen; ((Component)_voteYes).gameObject.SetActive(flag2); ((Component)_voteNo).gameObject.SetActive(flag2); ((Component)_dismissVote).gameObject.SetActive(flag); ((Selectable)_dismissVote).interactable = flag; float y = 41f; y = PlaceVoteText(_voteStatus, y, 7f); y = PlaceVoteText(_voteTally, y, 7f); y = PlaceVoteText(_voteHint, y, 10f); if (flag2) { ((RectTransform)((Component)_voteYes).transform).anchoredPosition = new Vector2(12f, 0f - y); ((RectTransform)((Component)_voteNo).transform).anchoredPosition = new Vector2(126f, 0f - y); y += 32f; } else if (flag) { ((RectTransform)((Component)_dismissVote).transform).anchoredPosition = new Vector2(12f, 0f - y); y += 32f; } _voteBox.sizeDelta = new Vector2(240f, y + 12f); RefreshVoteVisibility(); } } private static float PlaceVoteText(Text text, float y, float gap) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) //IL_006b: Unknown result type (might be due to invalid IL or missing references) TextGenerationSettings generationSettings = text.GetGenerationSettings(new Vector2(216f, 0f)); float num = Mathf.Max((float)text.fontSize * 1.35f, text.cachedTextGeneratorForLayout.GetPreferredHeight(text.text, generationSettings) / text.pixelsPerUnit + 3f); ((Graphic)text).rectTransform.anchoredPosition = new Vector2(12f, 0f - y); ((Graphic)text).rectTransform.sizeDelta = new Vector2(216f, num); return y + num + gap; } private void RefreshVoteVisibility() { if ((Object)(object)_voteOverlay != (Object)null) { _voteOverlay.SetActive(HasGameplaySession() && _state != null && _state.VoteVisible && !_state.SessionReloading && !VoteResultDismissed()); } } private bool VoteResultDismissed() { if (_state != null && _state.VoteClosed && !_state.VoteActive && _dismissedVoteId == _state.VoteId) { return string.Equals(_dismissedVoteEpoch, _state.VoteEpoch, StringComparison.Ordinal); } return false; } private void DismissVoteResult() { if (_state != null && _state.VoteVisible && _state.VoteClosed && !_state.VoteActive) { _dismissedVoteEpoch = _state.VoteEpoch; _dismissedVoteId = _state.VoteId; RefreshVoteVisibility(); } } private void BuildPanel(Transform parent) { //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown //IL_00fc: Unknown result type (might be due to invalid IL or missing references) //IL_0112: 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_0134: Unknown result type (might be due to invalid IL or missing references) //IL_0154: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Unknown result type (might be due to invalid IL or missing references) //IL_01b1: Unknown result type (might be due to invalid IL or missing references) //IL_01e6: Unknown result type (might be due to invalid IL or missing references) //IL_0220: Unknown result type (might be due to invalid IL or missing references) //IL_022a: Expected O, but got Unknown //IL_025a: Unknown result type (might be due to invalid IL or missing references) //IL_0264: Expected O, but got Unknown //IL_0294: Unknown result type (might be due to invalid IL or missing references) //IL_029e: Expected O, but got Unknown //IL_02ce: Unknown result type (might be due to invalid IL or missing references) //IL_02d8: Expected O, but got Unknown //IL_0308: Unknown result type (might be due to invalid IL or missing references) //IL_0312: Expected O, but got Unknown //IL_03b6: Unknown result type (might be due to invalid IL or missing references) //IL_03da: Unknown result type (might be due to invalid IL or missing references) //IL_0447: Unknown result type (might be due to invalid IL or missing references) //IL_0493: Unknown result type (might be due to invalid IL or missing references) //IL_04b0: Unknown result type (might be due to invalid IL or missing references) //IL_04d3: Unknown result type (might be due to invalid IL or missing references) //IL_04fd: Unknown result type (might be due to invalid IL or missing references) //IL_0585: Unknown result type (might be due to invalid IL or missing references) //IL_058f: Expected O, but got Unknown //IL_0628: Unknown result type (might be due to invalid IL or missing references) //IL_0677: Unknown result type (might be due to invalid IL or missing references) //IL_06a4: Unknown result type (might be due to invalid IL or missing references) //IL_06ae: Expected O, but got Unknown //IL_0784: Unknown result type (might be due to invalid IL or missing references) //IL_07ae: Unknown result type (might be due to invalid IL or missing references) //IL_07ef: Unknown result type (might be due to invalid IL or missing references) //IL_0810: Unknown result type (might be due to invalid IL or missing references) //IL_091d: Unknown result type (might be due to invalid IL or missing references) //IL_0927: Expected O, but got Unknown //IL_094d: Unknown result type (might be due to invalid IL or missing references) //IL_096e: Unknown result type (might be due to invalid IL or missing references) //IL_09b5: Unknown result type (might be due to invalid IL or missing references) //IL_09d3: Unknown result type (might be due to invalid IL or missing references) //IL_09f0: Unknown result type (might be due to invalid IL or missing references) //IL_0a37: Unknown result type (might be due to invalid IL or missing references) //IL_0a58: Unknown result type (might be due to invalid IL or missing references) //IL_0a9f: Unknown result type (might be due to invalid IL or missing references) //IL_0abd: Unknown result type (might be due to invalid IL or missing references) //IL_0af6: Unknown result type (might be due to invalid IL or missing references) //IL_0b33: Unknown result type (might be due to invalid IL or missing references) //IL_0b51: Unknown result type (might be due to invalid IL or missing references) //IL_0b92: Unknown result type (might be due to invalid IL or missing references) //IL_0bb3: Unknown result type (might be due to invalid IL or missing references) //IL_0bfa: Unknown result type (might be due to invalid IL or missing references) //IL_0c37: Unknown result type (might be due to invalid IL or missing references) //IL_0c59: Unknown result type (might be due to invalid IL or missing references) //IL_0ca0: Unknown result type (might be due to invalid IL or missing references) //IL_0cbe: Unknown result type (might be due to invalid IL or missing references) //IL_0d10: Unknown result type (might be due to invalid IL or missing references) //IL_0d4d: Unknown result type (might be due to invalid IL or missing references) //IL_0d6b: Unknown result type (might be due to invalid IL or missing references) //IL_0d87: Unknown result type (might be due to invalid IL or missing references) //IL_0d97: Expected O, but got Unknown //IL_0dac: Unknown result type (might be due to invalid IL or missing references) //IL_0dbc: Expected O, but got Unknown //IL_0dd1: Unknown result type (might be due to invalid IL or missing references) //IL_0de1: Expected O, but got Unknown //IL_0df4: Unknown result type (might be due to invalid IL or missing references) //IL_0e18: Unknown result type (might be due to invalid IL or missing references) //IL_0e38: Unknown result type (might be due to invalid IL or missing references) //IL_0e48: Expected O, but got Unknown //IL_0e5b: Unknown result type (might be due to invalid IL or missing references) //IL_0e77: Unknown result type (might be due to invalid IL or missing references) //IL_0e97: Unknown result type (might be due to invalid IL or missing references) //IL_0ea7: Expected O, but got Unknown //IL_0ebc: Unknown result type (might be due to invalid IL or missing references) //IL_0ecc: Expected O, but got Unknown //IL_0edf: Unknown result type (might be due to invalid IL or missing references) //IL_0efb: Unknown result type (might be due to invalid IL or missing references) //IL_0fa1: Unknown result type (might be due to invalid IL or missing references) //IL_0fd4: Unknown result type (might be due to invalid IL or missing references) //IL_105c: Unknown result type (might be due to invalid IL or missing references) //IL_107d: Unknown result type (might be due to invalid IL or missing references) //IL_10a2: Unknown result type (might be due to invalid IL or missing references) //IL_10e8: Unknown result type (might be due to invalid IL or missing references) //IL_10f2: Expected O, but got Unknown //IL_1112: Unknown result type (might be due to invalid IL or missing references) //IL_111c: Expected O, but got Unknown //IL_1174: Unknown result type (might be due to invalid IL or missing references) //IL_1191: Unknown result type (might be due to invalid IL or missing references) //IL_11b3: Unknown result type (might be due to invalid IL or missing references) //IL_11d0: Unknown result type (might be due to invalid IL or missing references) //IL_1217: Unknown result type (might be due to invalid IL or missing references) //IL_1251: Unknown result type (might be due to invalid IL or missing references) //IL_125b: Expected O, but got Unknown //IL_128b: Unknown result type (might be due to invalid IL or missing references) //IL_1295: Expected O, but got Unknown //IL_12c5: Unknown result type (might be due to invalid IL or missing references) //IL_12cf: Expected O, but got Unknown RectTransform val = Stretch(parent, "Control overlay"); _panelContainer = ((Component)val).gameObject; _panelGroup = ((Component)val).gameObject.AddComponent(); _panelGroup.alpha = 0f; _panelGroup.blocksRaycasts = false; _panelGroup.interactable = false; RectTransform val2 = Stretch((Transform)(object)val, "Backdrop"); Image targetGraphic = Image(val2, new Color(0f, 0f, 0f, 0.57f), raycast: true); Button val3 = ((Component)val2).gameObject.AddComponent