using System; using System.Buffers.Binary; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; using System.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Text.Json; using System.Text.Json.Serialization; using System.Text.RegularExpressions; using HarmonyLib; using Il2Cpp; using Il2CppFishNet; using Il2CppFishNet.Connection; using Il2CppFishNet.Managing; using Il2CppFishNet.Managing.Client; using Il2CppFishNet.Managing.Object; using Il2CppFishNet.Managing.Server; using Il2CppFishNet.Object; using Il2CppInterop.Runtime; using Il2CppInterop.Runtime.InteropTypes; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne; using Il2CppScheduleOne.Calling; using Il2CppScheduleOne.Cartel; using Il2CppScheduleOne.Core.Items.Framework; using Il2CppScheduleOne.Delivery; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Doors; using Il2CppScheduleOne.Economy; using Il2CppScheduleOne.Employees; using Il2CppScheduleOne.EntityFramework; using Il2CppScheduleOne.GameTime; using Il2CppScheduleOne.Interaction; using Il2CppScheduleOne.ItemFramework; using Il2CppScheduleOne.Law; using Il2CppScheduleOne.Management; using Il2CppScheduleOne.Map; using Il2CppScheduleOne.Messaging; using Il2CppScheduleOne.NPCs; using Il2CppScheduleOne.NPCs.Behaviour; using Il2CppScheduleOne.ObjectScripts; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.Persistence.Datas; using Il2CppScheduleOne.Persistence.Loaders; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.Police; using Il2CppScheduleOne.Product; using Il2CppScheduleOne.Product.Packaging; using Il2CppScheduleOne.Property; using Il2CppScheduleOne.ScriptableObjects; using Il2CppScheduleOne.Storage; using Il2CppScheduleOne.Tiles; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.Management; using Il2CppScheduleOne.UI.Phone; using Il2CppScheduleOne.UI.Phone.Delivery; using Il2CppScheduleOne.Vehicles; using Il2CppSystem; using Il2CppSystem.Collections.Generic; using Il2CppSystem.Reflection; using MelonLoader; using MelonLoader.Preferences; using Microsoft.CodeAnalysis; using OrganizedCrime; using OrganizedCrime.Model; using OrganizedCrime.Persistence; using OrganizedCrime.Runtime; using S1API.Building; using S1API.DeadDrops; using S1API.Entities; using S1API.GameTime; using S1API.Items; using S1API.Lifecycle; using S1API.Messaging; using S1API.Money; using S1API.PhoneCalls; using S1API.Products; using S1API.Quests; using S1API.Quests.Constants; using S1API.Storages; using UnityEngine; using UnityEngine.AI; using UnityEngine.Events; using UnityEngine.Rendering; using UnityEngine.SceneManagement; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(OrganizedCrimeMod), "Organized Crime", "1.0.0", "MadJag Studios", null)] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyVersion("0.0.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace OrganizedCrime { public sealed class OrganizedCrimeMod : MelonMod { private sealed class FishWarehousePersistenceReplayOperations : IFishWarehousePersistenceReplayOperations { private readonly RuntimePropertyHost _propertyHost; private readonly FishWarehouseRuntimeService _runtimeService; private readonly FishWarehousePersistenceService _persistenceService; private readonly FishWarehousePropertyCaptureService _captureService; private readonly Action _log; private FishWarehousePropertyReplayHost? _objectReplay; private FishWarehouseEmployeeReplayHost? _employeeReplay; public bool IsRuntimeReady => _propertyHost.PersistenceRuntimeReady; public bool IsNavigationReady => _runtimeService.EmployeeNavigationReady; public FishWarehousePersistenceReplayOperations(RuntimePropertyHost propertyHost, FishWarehouseRuntimeService runtimeService, FishWarehousePersistenceService persistenceService, FishWarehousePropertyCaptureService captureService, Action log) { _propertyHost = propertyHost; _runtimeService = runtimeService; _persistenceService = persistenceService; _captureService = captureService; _log = log; } public void ConfigureReplay(bool objectReplayRequired, int? employeeAgentTypeId) { _propertyHost.ConfigurePersistenceReplay(objectReplayRequired, employeeAgentTypeId); } public void RequestOwnedRestore() { _runtimeService.RequestLoadedOwnedRestore(); } public void ResetRuntimeReferences() { _objectReplay = null; _employeeReplay = null; } public FishWarehouseReplayOperationResult ReplayObjects() { PropertyData capturedPropertyData = _captureService.CapturedPropertyData; Property currentProperty = _propertyHost.CurrentProperty; if (capturedPropertyData == null || currentProperty == null) { return FishWarehouseReplayOperationResult.Failed("captured PropertyData or runtime Property was unavailable."); } if (_objectReplay == null) { if (!FishWarehouseNativePropertyReplayAdapter.TryCreateDescriptors(capturedPropertyData, out IReadOnlyList descriptors, out string failureReason)) { return FishWarehouseReplayOperationResult.Failed(failureReason ?? "object replay descriptors were unavailable."); } _objectReplay = new FishWarehousePropertyReplayHost(new FishWarehouseNativePropertyReplayAdapter(capturedPropertyData, currentProperty, _log), descriptors, null, _log); } FishWarehousePropertyReplayResult fishWarehousePropertyReplayResult = _objectReplay.Replay(); if (fishWarehousePropertyReplayResult.State == FishWarehousePropertyReplayState.Failed) { return FishWarehouseReplayOperationResult.Failed(fishWarehousePropertyReplayResult.FailureReason ?? "native object replay failed."); } if (fishWarehousePropertyReplayResult.State != FishWarehousePropertyReplayState.Succeeded) { return FishWarehouseReplayOperationResult.Pending(); } return FishWarehouseReplayOperationResult.Succeeded(fishWarehousePropertyReplayResult.ResolvedObjectCount); } public FishWarehouseReplayOperationResult MarkObjectsReplayed() { FishWarehousePropertyReplayHost? objectReplay = _objectReplay; if (objectReplay == null || objectReplay.Result.State != FishWarehousePropertyReplayState.Succeeded) { return FishWarehouseReplayOperationResult.Failed("native object replay was not complete before the runtime gate was marked."); } _runtimeService.MarkPersistenceObjectsReplayed(); string[] savedHomeGuids = (from record in _captureService.CapturedEmployeeRecords select record.BedGuid into guid where !string.IsNullOrWhiteSpace(guid) select guid).ToArray(); switch (_propertyHost.AssessRestoredEmployeeHome((IReadOnlyCollection?)(object)savedHomeGuids)) { case FishWarehouseEmployeeHomeAdoptionResult.MultipleFound: return FishWarehouseReplayOperationResult.Failed("multiple restored Fish Warehouse employee homes were found; authored fallback was not permitted."); case FishWarehouseEmployeeHomeAdoptionResult.Failed: return FishWarehouseReplayOperationResult.Failed("restored Fish Warehouse employee-home adoption failed."); case FishWarehouseEmployeeHomeAdoptionResult.Adopted: _runtimeService.MarkPersistenceEmployeeHomeRestored(); break; default: { FishWarehouseSaveState? currentState = _persistenceService.CurrentState; if ((object)currentState != null && currentState.EmployeeHomePlaced && !_runtimeService.TryPlaceEmployeeHome()) { return FishWarehouseReplayOperationResult.Failed("saved Fish Warehouse employee home was not restored and authored fallback placement failed."); } break; } } return FishWarehouseReplayOperationResult.Succeeded(_objectReplay.Result.ResolvedObjectCount); } public FishWarehouseReplayOperationResult ReplayEmployees() { PropertyData capturedPropertyData = _captureService.CapturedPropertyData; Property currentProperty = _propertyHost.CurrentProperty; if (capturedPropertyData == null || currentProperty == null) { return FishWarehouseReplayOperationResult.Failed("captured PropertyData or runtime Property was unavailable."); } if (_employeeReplay == null) { IReadOnlyList capturedEmployees = FishWarehousePropertyCaptureService.CreateEmployeeReplayDescriptors(_captureService.CapturedEmployeeRecords); if (!_propertyHost.TryCreateEmployeeReplayAdapter(capturedPropertyData, out IFishWarehouseNativeEmployeeReplayAdapter adapter, out string failureReason, _persistenceService.CurrentState?.Replay?.InventoryRestoredEmployeeGuids)) { return FishWarehouseReplayOperationResult.Failed(failureReason ?? "employee replay adapter was unavailable."); } _employeeReplay = new FishWarehouseEmployeeReplayHost(adapter, capturedEmployees, _persistenceService.CurrentState?.UnsupportedEmployees ?? Array.Empty(), null, _log); } FishWarehouseEmployeeReplayResult fishWarehouseEmployeeReplayResult = _employeeReplay.Replay(IsNavigationReady); if (fishWarehouseEmployeeReplayResult.State == FishWarehouseEmployeeReplayState.Pending) { return new FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState.Pending, 0, null, fishWarehouseEmployeeReplayResult.InventoryRestoredEmployeeGuids); } if (fishWarehouseEmployeeReplayResult.State == FishWarehouseEmployeeReplayState.Failed || !fishWarehouseEmployeeReplayResult.CanAdvanceEmployeesReplayed) { return new FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState.Failed, 0, fishWarehouseEmployeeReplayResult.FailureReason ?? fishWarehouseEmployeeReplayResult.PrimaryFailureReason ?? "supported employee replay did not complete.", fishWarehouseEmployeeReplayResult.InventoryRestoredEmployeeGuids); } if (!_persistenceService.TryMergeUnsupportedEmployees(fishWarehouseEmployeeReplayResult.UnsupportedEmployees)) { return FishWarehouseReplayOperationResult.Failed("unsupported employee records could not be persisted."); } _log($"Fish Warehouse employee replay summary: {fishWarehouseEmployeeReplayResult.ReplayedEmployeeCount}/{fishWarehouseEmployeeReplayResult.SupportedCapturedEmployeeCount} supported employees replayed."); return new FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState.Succeeded, fishWarehouseEmployeeReplayResult.ReplayedEmployeeCount, null, fishWarehouseEmployeeReplayResult.InventoryRestoredEmployeeGuids); } public FishWarehouseDeliveryRestoreFlushResult ReleaseDeliveries() { return FishWarehouseDeliveryRestorePatch.FlushIfReady(); } } private FishWarehouseRuntimeService? _fishWarehouseRuntimeService; private RuntimePropertyHost? _runtimePropertyHost; private FishWarehousePersistenceService? _fishWarehousePersistenceService; private FishWarehousePropertyCaptureService? _fishWarehousePropertyCaptureService; private FishWarehousePersistenceReplayService? _fishWarehousePersistenceReplayService; private LocalPressureRuntimeComposition? _localPressureRuntime; private NativeLawResponseModComposition? _nativeLawResponseComposition; private NativeLawResponseController? _nativeLawResponseController; private PoliceCustodyEvidenceBridge? _policeCustodyEvidenceBridge; private SyndicateHqHostContextAdapter? _syndicateHqHostContext; private Release1StoryRuntimeService? _release1StoryRuntime; private Release1ProductionComposition? _release1ProductionComposition; private Release1ChiefComposition? _release1ChiefComposition; private SyndicateHqRuntimeComposition? _syndicateHqComposition; private SyndicateHqNativeStorageRuntime? _syndicateHqStorageRuntime; private IRelease1SmallCourtesyWorld? _release1SmallCourtesyWorld; private bool _initialized; private bool _ownerQaKeysEnabled; private bool _theEnvelopeCashGateDumpNext = true; private bool _theEnvelopeClosetCashGateDumpNext = true; private bool _keepTheLightsOffProductionGateDumpNext = true; private OrganizedCrimeTimingReceipts _timing = OrganizedCrimeTimingReceipts.Disabled; public override void OnInitializeMelon() { if (_initialized) { MelonLogger.Warning("[Organized Crime] duplicate initialization was ignored."); return; } _initialized = true; OrganizedCrimeLog.Warning = delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }; OrganizedCrimeLog.Receipt = delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }; MelonLogger.Msg("[Organized Crime] production runtime shell loaded."); _ownerQaKeysEnabled = OwnerQaKeysPreference.Read(); _timing = new OrganizedCrimeTimingReceipts((Action?)MelonLogger.Msg, TimingReceiptThresholdPreference.Read()); if (_timing.IsEnabled) { MelonLogger.Msg($"[Organized Crime] Timing receipts report any phase at or above {_timing.ThresholdMs} ms."); } if (_ownerQaKeysEnabled) { MelonLogger.Msg("[Organized Crime] Owner QA keys are enabled by preference; F8 recognition and fish warehouse developer keys are live."); } SyndicateHqHostContextAdapter syndicateHqHostContextAdapter = (_syndicateHqHostContext = new SyndicateHqHostContextAdapter()); _release1StoryRuntime = new Release1StoryRuntimeService(syndicateHqHostContextAdapter, CreateRelease1StoryRepository); Release1PresentationMode release1PresentationMode = Release1PresentationModePreference.Read(); S1ApiRelease1NativePresentation native = ((release1PresentationMode == Release1PresentationMode.Native) ? new S1ApiRelease1NativePresentation(Release1QuestPersistencePolicyPreference.Read(), delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }) : null); SyndicateHqNativeStorageRuntime syndicateHqNativeStorageRuntime = (_syndicateHqStorageRuntime = new SyndicateHqNativeStorageRuntime(new SyndicateHqNativeStorageBoundary { Timing = _timing }, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }, OrganizedCrimeLog.Receipt)); S1ApiRelease1SmallCourtesyWorld s1ApiRelease1SmallCourtesyWorld = (S1ApiRelease1SmallCourtesyWorld)(_release1SmallCourtesyWorld = new S1ApiRelease1SmallCourtesyWorld(syndicateHqHostContextAdapter, syndicateHqNativeStorageRuntime, null, ((MelonBase)this).HarmonyInstance)); SyndicateHqDoorObserver hqObserver = null; _release1ProductionComposition = new Release1ProductionComposition(_release1StoryRuntime, new Release1PostBenziesUnlockReader(syndicateHqHostContextAdapter, new S1CartelStatusSource()), new S1ApiRelease1PhoneCallQueue(), s1ApiRelease1SmallCourtesyWorld, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }, null, null, null, release1PresentationMode, native, delegate { SyndicateHqDoorFingerprint syndicateHqDoorFingerprint = hqObserver?.CurrentFingerprint; return ((object)syndicateHqDoorFingerprint == null || !syndicateHqDoorFingerprint.Position.IsFinite) ? null : new Release1WorldPoint(syndicateHqDoorFingerprint.Position.X, syndicateHqDoorFingerprint.Position.Y, syndicateHqDoorFingerprint.Position.Z); }); _release1StoryRuntime.Timing = _timing; _release1ProductionComposition.Timing = _timing; _release1ProductionComposition.KeepTheLightsOff.Service.Timing = _timing; _release1ProductionComposition.TheEnvelope.Service.Timing = _timing; if (!SyndicateHqStorageLoadPatch.Apply(((MelonBase)this).HarmonyInstance, syndicateHqNativeStorageRuntime, new SyndicateHqStorageLoadContextAdapter(), delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }, OrganizedCrimeLog.Receipt)) { MelonLogger.Warning("[Organized Crime] Native Syndicate HQ storage will remain inert because its exact vanilla load prefix was unavailable."); } SyndicateHqRuntimeService hqRuntime = null; hqObserver = new SyndicateHqDoorObserver(delegate(SyndicateHqDoorFingerprint fingerprint, long generation) { hqRuntime?.HandleDoorInteraction(fingerprint, generation); }, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }) { Timing = _timing }; SyndicateHqInteriorRuntime interior = new SyndicateHqInteriorRuntime(delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }, syndicateHqNativeStorageRuntime) { Timing = _timing }; hqRuntime = new SyndicateHqRuntimeService(syndicateHqHostContextAdapter, new Release1StorySyndicateHqUnlockAuthority(syndicateHqHostContextAdapter, _release1StoryRuntime), hqObserver, interior, new SyndicateHqNativePlayerTransport(delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }), delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }); _syndicateHqComposition = new SyndicateHqRuntimeComposition(hqRuntime, delegate { _syndicateHqHostContext?.BeginLoad(); }); FishWarehouseManagementCanvasPatch.Apply(((MelonBase)this).HarmonyInstance); FishWarehouseNativeGridPlacementPatches.Apply(((MelonBase)this).HarmonyInstance); FishWarehouseDockOccupantRefreshPatch.Apply(((MelonBase)this).HarmonyInstance); RuntimePropertyHost propertyHost = new RuntimePropertyHost(); _runtimePropertyHost = propertyHost; FishWarehouseDeliveryRestorePatch.Apply(((MelonBase)this).HarmonyInstance, () => propertyHost.CanRestoreDeliveries, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }); _fishWarehouseRuntimeService = new FishWarehouseRuntimeService(propertyHost, RuntimePropertyReadiness.IsFishWarehouseReady, propertyHost.TryPlaceEmployeeHome, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }); FishWarehousePropertySnapshotStore snapshotStore = new FishWarehousePropertySnapshotStore(); FishWarehousePropertyCaptureService captureService = null; FishWarehouseReplayLifecycle fishWarehouseReplayLifecycle = new FishWarehouseReplayLifecycle((FishWarehouseReplayCheckpoint checkpoint) => captureService?.GetRecoveryPrerequisites(checkpoint) ?? new FishWarehouseCaptureRecoveryPrerequisites(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false)); _fishWarehousePersistenceService = new FishWarehousePersistenceService(new FishWarehouseSaveStore(), GetActiveSaveFolder, () => _fishWarehouseRuntimeService?.CaptureSaveState() ?? FishWarehouseSaveState.UnownedState(), ApplyLoadedSaveState, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }, snapshotStore, fishWarehouseReplayLifecycle); captureService = new FishWarehousePropertyCaptureService(snapshotStore, new FishWarehouseNativePropertySnapshotInspector(), _fishWarehousePersistenceService, fishWarehouseReplayLifecycle, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }); _fishWarehousePropertyCaptureService = captureService; FishWarehouseRuntimeService runtimeService = _fishWarehouseRuntimeService; FishWarehousePersistenceService persistenceService = _fishWarehousePersistenceService; FishWarehousePersistenceReplayOperations replayOperations = new FishWarehousePersistenceReplayOperations(propertyHost, runtimeService, persistenceService, captureService, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }); _fishWarehousePersistenceReplayService = new FishWarehousePersistenceReplayService(fishWarehouseReplayLifecycle, GetActiveSaveFolder, captureService.TryRecoverAtLoadComplete, persistenceService.HandleLoadComplete, () => persistenceService.CurrentState, replayOperations, persistenceService.TryPersistReplayCheckpoint, delegate { captureService.ResetRuntimeReferences(); replayOperations.ResetRuntimeReferences(); persistenceService.PrimeStateForLoad(); }, FishWarehouseDeliveryRestorePatch.Reset, delegate { if (!runtimeService.PrepareForLoad()) { MelonLogger.Warning("[Organized Crime] Fish Warehouse runtime Property could not be fully prepared for the next save load."); } }, null, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }); FishWarehousePropertyLoadCapturePatch.Apply(((MelonBase)this).HarmonyInstance, IsAuthoritativeHost, GetActiveSaveFolder, captureService, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }); FishWarehouseNativePropertyWriteGuardPatch.Apply(((MelonBase)this).HarmonyInstance, IsAuthoritativeHost, () => captureService.IsDegraded); GameLifecycle.OnSaveComplete += HandleSaveComplete; GameLifecycle.OnSaveStart += HandleSaveStart; GameLifecycle.OnPreLoad += HandlePreLoad; GameLifecycle.OnLoadComplete += HandleLoadComplete; GameLifecycle.OnPreSceneChange += HandlePreSceneChange; _nativeLawResponseComposition = new NativeLawResponseModComposition(CreateLocalPressureRepository, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }, delegate(string message) { MelonLogger.Msg("[Organized Crime] " + message); }, OrganizedCrimeLog.Receipt); _nativeLawResponseController = _nativeLawResponseComposition.Controller; _localPressureRuntime = _nativeLawResponseComposition.LocalPressureRuntime; _policeCustodyEvidenceBridge = new PoliceCustodyEvidenceBridge(new CustodyEntryGate(), _localPressureRuntime.Service, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }); PoliceCustodyPatch.Apply(((MelonBase)this).HarmonyInstance, _policeCustodyEvidenceBridge, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }); S1ApiRelease1NativePresentation native2 = ((release1PresentationMode == Release1PresentationMode.Native) ? new S1ApiRelease1NativePresentation(Release1QuestPersistencePolicyPreference.Read(), "oc_release1_chief_campbell", () => (NPC)(object)new Release1ChiefCampbellNpc(), (NPC npc) => npc is Release1ChiefCampbellNpc, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }) : null); LocalPressureRuntimeService localPressureService = _localPressureRuntime.Service; _release1ChiefComposition = new Release1ChiefComposition(_release1StoryRuntime, s1ApiRelease1SmallCourtesyWorld, _nativeLawResponseComposition.ChiefObserver, (string playerId) => (!localPressureService.TryGetState(playerId, out LocalPressureState state)) ? null : state, (string playerId) => localPressureService.TryApplyRecordWipe(playerId, localPressureService.SessionEpoch, localPressureService.LoadEpoch), delegate(out Guid sessionEpoch, out long loadEpoch) { sessionEpoch = localPressureService.SessionEpoch; loadEpoch = localPressureService.LoadEpoch; return localPressureService.Phase == LocalPressureRuntimePhase.Active; }, release1PresentationMode, native2, delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }); } public override void OnDeinitializeMelon() { Release1LockdownGatePatch.Reset(); _syndicateHqStorageRuntime?.MarkTeardown(); if (_syndicateHqComposition != null && !_syndicateHqComposition.TryDispose()) { MelonLogger.Warning("[Organized Crime] Syndicate HQ deinitialization remains pending until exact listener cleanup succeeds."); } _syndicateHqComposition = null; SyndicateHqStorageLoadPatch.Reset(); _syndicateHqStorageRuntime?.Dispose(); _syndicateHqStorageRuntime = null; _release1ProductionComposition?.Dispose(); _release1ProductionComposition = null; _release1ChiefComposition?.Dispose(); _release1ChiefComposition = null; _release1SmallCourtesyWorld = null; _release1StoryRuntime?.Dispose(); _release1StoryRuntime = null; _syndicateHqHostContext = null; PoliceCustodyPatch.Reset(); _policeCustodyEvidenceBridge?.Dispose(); _policeCustodyEvidenceBridge = null; _nativeLawResponseComposition?.Dispose(); _nativeLawResponseComposition = null; _nativeLawResponseController = null; _localPressureRuntime = null; FishWarehouseManagementCanvasPatch.SetTargetProperty(null); FishWarehouseDeliveryRestorePatch.Reset(); FishWarehousePropertyLoadCapturePatch.Reset(); FishWarehouseNativePropertyWriteGuardPatch.Reset(); GameLifecycle.OnSaveComplete -= HandleSaveComplete; GameLifecycle.OnSaveStart -= HandleSaveStart; GameLifecycle.OnPreLoad -= HandlePreLoad; GameLifecycle.OnLoadComplete -= HandleLoadComplete; GameLifecycle.OnPreSceneChange -= HandlePreSceneChange; _fishWarehousePersistenceReplayService = null; _fishWarehousePersistenceService = null; _fishWarehousePropertyCaptureService?.Reset(); _fishWarehousePropertyCaptureService = null; _fishWarehouseRuntimeService = null; _runtimePropertyHost = null; _initialized = false; } public override void OnUpdate() { //IL_05c9: Unknown result type (might be due to invalid IL or missing references) //IL_05e3: 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_0349: Unknown result type (might be due to invalid IL or missing references) //IL_0426: Unknown result type (might be due to invalid IL or missing references) _syndicateHqComposition?.Update(); _release1ProductionComposition?.Update(); _release1ChiefComposition?.Update(); _localPressureRuntime?.Service.PumpReadiness(); if (IsAuthoritativeHost()) { _fishWarehouseRuntimeService?.Update(); _fishWarehousePersistenceReplayService?.Update(); } _runtimePropertyHost?.EnsureManagementWorldspaceUiContainerForOpenCanvas(); if (!_ownerQaKeysEnabled) { return; } if (Input.GetKeyDown((KeyCode)92)) { _fishWarehouseRuntimeService?.ToggleDeveloperLifecycle(); } if (Input.GetKeyDown((KeyCode)111)) { _fishWarehouseRuntimeService?.TryUnlockDeveloperLifecycle(); } if (Input.GetKeyDown((KeyCode)112)) { _fishWarehouseRuntimeService?.TryPlaceEmployeeHome(); } if (Input.GetKeyDown((KeyCode)289) && _syndicateHqHostContext != null && _release1StoryRuntime != null) { Release1StoryOwnerQaRecognitionResult release1StoryOwnerQaRecognitionResult = Release1StoryOwnerQaRecognitionHarness.TryRecognize(_syndicateHqHostContext, _release1StoryRuntime); MelonLogger.Msg($"[Organized Crime] OC-48 owner QA recognition: {release1StoryOwnerQaRecognitionResult.Status}: {release1StoryOwnerQaRecognitionResult.Message}"); } if (Input.GetKeyDown((KeyCode)290) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult = Release1WrongAddressStagingHarness.TryStage(_release1SmallCourtesyWorld); MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: stage status {release1StagingHarnessResult.Status}"); foreach (string line in release1StagingHarnessResult.Lines) { MelonLogger.Msg("[Organized Crime] OC-52 staging proof: " + line); } } if (Input.GetKeyDown((KeyCode)291) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult2 = Release1WrongAddressStagingHarness.TryDump(_release1SmallCourtesyWorld); MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: dump status {release1StagingHarnessResult2.Status}"); foreach (string line2 in release1StagingHarnessResult2.Lines) { MelonLogger.Msg("[Organized Crime] OC-52 staging proof: " + line2); } try { List all = WorldStorageEntity.All; int value = all?.Count ?? 0; MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: world storage entities: {value}"); if (all != null) { Enumerator enumerator2 = all.GetEnumerator(); while (enumerator2.MoveNext()) { WorldStorageEntity current3 = enumerator2.Current; if ((Object)(object)current3 == (Object)null) { continue; } List itemSlots = ((StorageEntity)current3).ItemSlots; int value2 = itemSlots?.Count ?? 0; int num = 0; if (itemSlots != null) { Enumerator enumerator3 = itemSlots.GetEnumerator(); while (enumerator3.MoveNext()) { ItemSlot current4 = enumerator3.Current; if (current4 != null && current4.Quantity > 0) { num++; } } } MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: entity {current3.GUID} name {((Object)((Component)current3).gameObject).name} changed {current3.HasChanged} slots {value2} occupied {num}"); if (itemSlots == null) { continue; } for (int i = 0; i < itemSlots.Count; i++) { ItemSlot val = itemSlots[i]; if (val != null && val.Quantity > 0) { ItemInstance itemInstance = val.ItemInstance; string value3 = ((itemInstance == null) ? "null" : ((BaseItemInstance)itemInstance).ID); MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: entity {current3.GUID} slot {i} id {value3} quantity {val.Quantity}"); } } } } } catch (Exception ex) { MelonLogger.Msg("[Organized Crime] OC-52 staging proof: world storage dump failed: " + ex.Message); } try { List deadDrops = DeadDrop.DeadDrops; int value4 = deadDrops?.Count ?? 0; MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: dead drops: {value4}"); if (deadDrops != null) { Enumerator enumerator4 = deadDrops.GetEnumerator(); while (enumerator4.MoveNext()) { DeadDrop current5 = enumerator4.Current; if (!((Object)(object)current5 == (Object)null)) { WorldStorageEntity storage = current5.Storage; if ((Object)(object)storage == (Object)null) { MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: drop {current5.DeadDropName} guid {current5.GUID} storage null"); } else { MelonLogger.Msg($"[Organized Crime] OC-52 staging proof: drop {current5.DeadDropName} guid {current5.GUID} storage {storage.GUID} storageName {((Object)((Component)storage).gameObject).name}"); } } } } } catch (Exception ex2) { MelonLogger.Msg("[Organized Crime] OC-52 staging proof: dead drop dump failed: " + ex2.Message); } } if (Input.GetKeyDown((KeyCode)292) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult3 = Release1RoomWithNoNameHoldRoomHarness.TryDump(_release1SmallCourtesyWorld); MelonLogger.Msg($"[Organized Crime] OC-57 hold room proof: dump status {release1StagingHarnessResult3.Status}"); foreach (string line3 in release1StagingHarnessResult3.Lines) { MelonLogger.Msg("[Organized Crime] OC-57 hold room proof: " + line3); } } if (Input.GetKeyDown((KeyCode)287) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult4 = Release1ShortNoticeQuantityHarness.TryDecrementByTwo(_release1SmallCourtesyWorld); MelonLogger.Msg($"[Organized Crime] OC-58 quantity proof: status {release1StagingHarnessResult4.Status}"); foreach (string line4 in release1StagingHarnessResult4.Lines) { MelonLogger.Msg("[Organized Crime] OC-58 quantity proof: " + line4); } } if (Input.GetKeyDown((KeyCode)282) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult5 = (_theEnvelopeCashGateDumpNext ? Release1TheEnvelopeCashGateHarness.TryDumpCashSlots(_release1SmallCourtesyWorld) : Release1TheEnvelopeCashGateHarness.TryDecrementFirstCashSlot(_release1SmallCourtesyWorld)); string value5 = (_theEnvelopeCashGateDumpNext ? "dump" : "decrement"); MelonLogger.Msg($"[Organized Crime] OC-60 cash gate proof ({value5}): status {release1StagingHarnessResult5.Status}"); foreach (string line5 in release1StagingHarnessResult5.Lines) { MelonLogger.Msg("[Organized Crime] OC-60 cash gate proof: " + line5); } _theEnvelopeCashGateDumpNext = !_theEnvelopeCashGateDumpNext; } if (Input.GetKeyDown((KeyCode)283) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult6 = (_theEnvelopeClosetCashGateDumpNext ? Release1TheEnvelopeClosetCashGateHarness.TryDumpClosetCashSlots(_release1SmallCourtesyWorld) : Release1TheEnvelopeClosetCashGateHarness.TryDecrementFirstClosetCashSlot(_release1SmallCourtesyWorld)); string value6 = (_theEnvelopeClosetCashGateDumpNext ? "dump" : "decrement"); MelonLogger.Msg($"[Organized Crime] OC-61 closet cash gate proof ({value6}): status {release1StagingHarnessResult6.Status}"); foreach (string line6 in release1StagingHarnessResult6.Lines) { MelonLogger.Msg("[Organized Crime] OC-61 closet cash gate proof: " + line6); } _theEnvelopeClosetCashGateDumpNext = !_theEnvelopeClosetCashGateDumpNext; } if (Input.GetKeyDown((KeyCode)278) && _release1SmallCourtesyWorld != null) { Release1StagingHarnessResult release1StagingHarnessResult7 = (_keepTheLightsOffProductionGateDumpNext ? Release1KeepTheLightsOffProductionGateHarness.TryDumpProductionActivity(_release1SmallCourtesyWorld) : Release1KeepTheLightsOffProductionGateHarness.TryCrossCheckUnpaidEmployees(_release1SmallCourtesyWorld)); string value7 = (_keepTheLightsOffProductionGateDumpNext ? "dump" : "cross check"); MelonLogger.Msg($"[Organized Crime] OC-65 production gate proof ({value7}): status {release1StagingHarnessResult7.Status}"); foreach (string line7 in release1StagingHarnessResult7.Lines) { MelonLogger.Msg("[Organized Crime] OC-65 production gate proof: " + line7); } _keepTheLightsOffProductionGateDumpNext = !_keepTheLightsOffProductionGateDumpNext; } if (Input.GetKeyDown((KeyCode)279) && _release1SmallCourtesyWorld != null) { string reason; Release1SmallCourtesyWorldMutationStatus value8 = (Release1LockdownGatePatch.LockdownActive ? _release1SmallCourtesyWorld.TryReleaseLockdown(out reason) : _release1SmallCourtesyWorld.TryEngageLockdown(out reason)); MelonLogger.Msg($"[Organized Crime] OC-73 lockdown gate: status {value8}"); if (!string.IsNullOrEmpty(reason)) { MelonLogger.Msg("[Organized Crime] OC-73 lockdown gate: " + reason); } } if (!Input.GetKeyDown((KeyCode)277)) { return; } Release1StagingHarnessResult release1StagingHarnessResult8 = Release1NpcRegistryDumpHarness.TryDump(); MelonLogger.Msg($"[Organized Crime] OC-56 npc registry: status {release1StagingHarnessResult8.Status}"); foreach (string line8 in release1StagingHarnessResult8.Lines) { MelonLogger.Msg("[Organized Crime] OC-56 npc registry: " + line8); } } public override void OnGUI() { _syndicateHqComposition?.OnGUI(); _release1ProductionComposition?.OnGUI(); } private void HandleSaveComplete() { _timing.Measure("save-complete", delegate { FishWarehousePersistenceReplayService.RunIfAuthoritativeHost(IsAuthoritativeHost(), delegate { _timing.Measure("save-complete/story", delegate { Release1StoryLifecycleLogging.LogIfRejected("OnSaveComplete", _release1StoryRuntime?.OnSaveComplete()); }); _timing.Measure("save-complete/production", delegate { _release1ProductionComposition?.OnSaveComplete(); }); _timing.Measure("save-complete/chief", delegate { _release1ChiefComposition?.OnSaveComplete(); }); _timing.Measure("save-complete/hq", delegate { _syndicateHqComposition?.OnSaveComplete(); }); _timing.Measure("save-complete/fish-warehouse", delegate { _fishWarehousePersistenceService?.HandleSaveComplete(); }); }); }); } private void HandleSaveStart() { _timing.Measure("save-start", delegate { _timing.Measure("save-start/story", delegate { Release1StoryLifecycleLogging.LogIfRejected("OnSaveStart", _release1StoryRuntime?.OnSaveStart()); }); _timing.Measure("save-start/production", delegate { _release1ProductionComposition?.OnSaveStart(); }); _timing.Measure("save-start/chief", delegate { _release1ChiefComposition?.OnSaveStart(); }); _timing.Measure("save-start/hq", delegate { _syndicateHqComposition?.OnSaveStart(); }); }); } private void HandlePreLoad() { _timing.Measure("pre-load", delegate { SyndicateHqStorageLoadPatch.BeginLoadCycle(); _timing.Measure("pre-load/hq", delegate { _syndicateHqComposition?.OnPreLoad(); }); _timing.Measure("pre-load/production", delegate { _release1ProductionComposition?.OnPreLoad(); }); _timing.Measure("pre-load/chief", delegate { _release1ChiefComposition?.OnPreLoad(); }); _timing.Measure("pre-load/story", delegate { Release1StoryLifecycleLogging.LogIfRejected("OnPreLoad", _release1StoryRuntime?.OnPreLoad()); }); FishWarehousePersistenceReplayService.RunIfAuthoritativeHost(IsAuthoritativeHost(), delegate { _timing.Measure("pre-load/fish-warehouse", delegate { _fishWarehousePersistenceReplayService?.PrepareForLoad(); }); }); }); } private void HandleLoadComplete() { _timing.Measure("load-complete", delegate { bool hqLoadPrepared = true; _timing.Measure("load-complete/hq-prepare", delegate { hqLoadPrepared = _syndicateHqComposition?.PrepareLoadComplete() ?? true; }); _timing.Measure("load-complete/story", delegate { Release1StoryLifecycleLogging.LogIfRejected("OnLoadComplete", _release1StoryRuntime?.OnLoadComplete()); }); _timing.Measure("load-complete/production", delegate { _release1ProductionComposition?.OnLoadComplete(); }); _timing.Measure("load-complete/chief", delegate { _release1ChiefComposition?.OnLoadComplete(); }); _timing.Measure("load-complete/hq-storage", delegate { SyndicateHqStorageLoadPatch.CompleteAfterVanillaLoad(); }); if (hqLoadPrepared) { _timing.Measure("load-complete/hq", delegate { _syndicateHqComposition?.OnLoadComplete(); }); } FishWarehousePersistenceReplayService.RunIfAuthoritativeHost(IsAuthoritativeHost(), delegate { _timing.Measure("load-complete/fish-warehouse", delegate { _fishWarehousePersistenceReplayService?.HandleLoadComplete(); }); }); }); if (!_ownerQaKeysEnabled || _release1SmallCourtesyWorld == null) { return; } try { foreach (string line in Release1WrongAddressStagingHarness.TryDump(_release1SmallCourtesyWorld).Lines) { if (line.StartsWith("dead drops read: ", StringComparison.Ordinal) || line.Contains(" quantity ", StringComparison.Ordinal)) { MelonLogger.Msg("[Organized Crime] OC-52 staging proof at load: " + line); } } } catch (Exception ex) { MelonLogger.Warning("[Organized Crime] OC-52 staging proof at load failed: " + ex.Message); } } private void HandlePreSceneChange() { _timing.Measure("pre-scene-change", delegate { _timing.Measure("pre-scene-change/production", delegate { _release1ProductionComposition?.OnPreSceneChange(); }); _timing.Measure("pre-scene-change/hq", delegate { _syndicateHqComposition?.OnPreSceneChange(); }); }); } private static string? GetActiveSaveFolder() { try { LoadManager instance = Singleton.Instance; return (instance != null) ? instance.LoadedGameFolderPath : null; } catch (Exception ex) { MelonLogger.Warning("[Organized Crime] Could not resolve the active save folder: " + ex.Message); return null; } } private static ILocalPressureStateRepository? CreateLocalPressureRepository(string? activeSaveFolder) { if (!LocalPressureSavePath.TryCreate(activeSaveFolder, out LocalPressureSavePath path, out LocalPressureSavePathResult _)) { return null; } return new LocalPressureStateStoreRepository(new LocalPressureStateStore(path)); } private static IRelease1StoryRepository CreateRelease1StoryRepository(string activeSaveFolder) { if (!Release1StorySavePath.TryCreate(activeSaveFolder, out Release1StorySavePath path, out Release1StorySavePathResult result)) { throw new InvalidOperationException(result.Message); } return new Release1StoryStateStoreRepository(new Release1StoryStateStore(path)); } private void ApplyLoadedSaveState(FishWarehouseSaveState state) { _fishWarehouseRuntimeService?.ApplyLoadedSaveState(state); } private static bool IsAuthoritativeHost() { try { ServerManager serverManager = InstanceFinder.ServerManager; return serverManager != null && serverManager.OneServerStarted(); } catch { return false; } } } } namespace OrganizedCrime.Runtime { public sealed class FishWarehouseBuildSurfaceHost { private const float GridPlaneOffset = 0.02f; private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private GameObject? _gridProxy; private Grid? _grid; private BuildableItem? _employeeHome; private EmployeeHome? _employeeHomeComponent; public bool IsReady { get { if (_gridProxy != null) { return _grid != null; } return false; } } public string GridGuid { get; private set; } = string.Empty; public int TileCount { get; private set; } public EmployeeHome? CurrentEmployeeHome => _employeeHomeComponent; public bool TryAdoptRestoredEmployeeHome(Property parentProperty, BuildableItem buildable, EmployeeHome employeeHome, Action? log = null) { if (_employeeHome != null) { if ((Object)(object)_employeeHome == (Object)(object)buildable) { return (Object)(object)_employeeHomeComponent == (Object)(object)employeeHome; } return false; } if (!IsReady || parentProperty == null || buildable == null || employeeHome == null) { log?.Invoke("Fish Warehouse restored employee-home adoption rejected because the native Grid, Property, BuildableItem, or EmployeeHome was unavailable."); return false; } try { if ((Object)(object)buildable.ParentProperty != (Object)(object)parentProperty || (Object)(object)((Component)employeeHome).GetComponent() != (Object)(object)buildable) { log?.Invoke("Fish Warehouse restored employee-home adoption rejected because the locker did not belong to the runtime Property."); return false; } _employeeHome = buildable; _employeeHomeComponent = employeeHome; return true; } catch (Exception ex) { log?.Invoke("Fish Warehouse restored employee-home adoption failed safely: " + ex.Message); return false; } } public bool TryPlaceEmployeeHome(Property parentProperty, FishWarehouseEmployeeHomeDefinition definition, Action? log = null) { //IL_009a: 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_0129: 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) if (_employeeHome != null) { return true; } if (!IsReady || parentProperty == null || (object)definition == null) { log?.Invoke("Fish Warehouse employee-home placement rejected because the native Grid or Property was unavailable."); return false; } BuildableItem val = null; try { if (!Guid.TryParse(definition.PlacementGuid, out var result) || result == Guid.Empty) { throw new InvalidOperationException("Employee-home placement GUID was invalid: " + definition.PlacementGuid); } GameObject obj = BuildManager.CreateGridItem((ItemManager.GetDefinition(definition.ItemId) ?? throw new InvalidOperationException("Vanilla buildable item was not found: " + definition.ItemId)).CreateInstance(1), _grid, new Vector2((float)definition.GridX, (float)definition.GridY), definition.Rotation, result.ToString()); BuildableItem val2 = ((obj != null) ? obj.GetComponent() : null); val = val2; EmployeeHome val3 = ((obj != null) ? obj.GetComponent() : null); if (val2 == null || val3 == null) { throw new InvalidOperationException("Vanilla locker did not produce both BuildableItem and EmployeeHome components."); } if ((Object)(object)val2.ParentProperty != (Object)(object)parentProperty) { Property parentProperty2 = val2.ParentProperty; throw new InvalidOperationException("Vanilla locker ParentProperty was not Fish Warehouse (actual=" + (((parentProperty2 != null) ? parentProperty2.PropertyCode : null) ?? "null") + ")."); } if (!Guid.TryParse(((object)val2.GUID/*cast due to .constrained prefix*/).ToString(), out var result2) || result2 == Guid.Empty) { throw new InvalidOperationException($"Vanilla locker returned an invalid placement GUID: {val2.GUID}"); } _employeeHome = val2; _employeeHomeComponent = val3; log?.Invoke($"Fish Warehouse employee locker placed (itemId={definition.ItemId}, guid={result2}, grid=({definition.GridX},{definition.GridY}), ParentProperty={parentProperty.PropertyCode})."); return true; } catch (Exception ex) { try { if (val != null) { val.Destroy_Server(); } } catch { } log?.Invoke("Fish Warehouse employee locker placement failed safely: " + ex.Message); return false; } } public bool TryAttach(Property parentProperty, Transform anchor, string gridRootName, string gridGuid, Action? log = null) { //IL_0093: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown //IL_0291: Unknown result type (might be due to invalid IL or missing references) //IL_0296: Unknown result type (might be due to invalid IL or missing references) //IL_02af: Unknown result type (might be due to invalid IL or missing references) //IL_02b4: Unknown result type (might be due to invalid IL or missing references) if (IsReady) { return true; } if (parentProperty == null || anchor == null || string.IsNullOrWhiteSpace(gridRootName) || string.IsNullOrWhiteSpace(gridGuid)) { return false; } GameObject val = null; GameObject val2 = null; Grid val3 = null; try { if (!Guid.TryParse(gridGuid, out var _)) { throw new InvalidOperationException("Fish Warehouse build Grid GUID was invalid: " + gridGuid); } int tileCount; Grid val4 = FindLargestOwnedGrid(out tileCount); if (val4 == null || ((Component)val4).gameObject == null) { throw new InvalidOperationException("No owned native Grid template was available."); } if (GridAlreadyUsesGuid(gridGuid)) { throw new InvalidOperationException("Fish Warehouse build Grid GUID was already present: " + gridGuid); } val = new GameObject(gridRootName + "_Staging"); val.transform.SetParent(((Component)parentProperty).transform, false); val.SetActive(false); val2 = Object.Instantiate(((Component)val4).gameObject, val.transform, false); ((Object)val2).name = gridRootName; val2.SetActive(false); Grid component = val2.GetComponent(); if (component == null) { throw new InvalidOperationException("Owned Grid template clone did not retain its Grid component."); } val3 = component; if (!TrySetMember(component, "_guid", gridGuid) || !TrySetMember(component, "_parentProperty", parentProperty)) { throw new InvalidOperationException("Could not prepare the Grid GUID and ParentProperty before Awake."); } Il2CppArrayBase componentsInChildren = val2.GetComponentsInChildren(true); if (componentsInChildren.Length == 0) { throw new InvalidOperationException("Owned Grid template clone contained no Tile components."); } PrepareTiles(component, Il2CppArrayBase.op_Implicit(componentsInChildren)); PositionGrid(val2.transform, ((Component)parentProperty).transform, Il2CppArrayBase.op_Implicit(componentsInChildren)); int rejectedTileCount; Tile[] array = BuildMeasuredTileSet(val2.transform, ((Component)parentProperty).transform, component, Il2CppArrayBase.op_Implicit(componentsInChildren), Grid.TileSize, out rejectedTileCount); if (array.Length == 0) { throw new InvalidOperationException("The measured Fish Warehouse footprint retained no native Grid cells."); } FishWarehouseEmployeeHomeDefinition employeeHomeDefinition = FishWarehouseEmployeeHomeDefinition.Default; if (!array.Any((Tile tile) => tile.x == employeeHomeDefinition.GridX && tile.y == employeeHomeDefinition.GridY)) { throw new InvalidOperationException($"The measured Fish Warehouse footprint excluded the restored employee-home cell ({employeeHomeDefinition.GridX},{employeeHomeDefinition.GridY})."); } DisableNonTileColliders(val2); AddBuildHoverCollider(val2.transform, array, Grid.TileSize); val2.SetActive(true); RebuildNativeTopology(component, array); Tile val5 = ((IEnumerable)array).FirstOrDefault((Func)((Tile tile) => tile != null && tile.x == 0 && tile.y == 0)); if (val5 == null || ((Component)val5).transform == null) { throw new InvalidOperationException("The rebuilt Fish Warehouse Grid lost its existing (0,0) reference cell."); } FishWarehouseNativeGridPlacementPatches.Register(component, ((Component)component).transform.InverseTransformPoint(((Component)val5).transform.position), Grid.TileSize); RegisterPropertyGrid(parentProperty, component); string text = ((object)component.GUID/*cast due to .constrained prefix*/).ToString(); if (!text.Equals(gridGuid, StringComparison.OrdinalIgnoreCase) || (Object)(object)component.ParentProperty != (Object)(object)parentProperty) { throw new InvalidOperationException($"Grid identity validation failed (actualGuid={text}, expectedGuid={gridGuid}, parent={((component.ParentProperty == null) ? "null" : component.ParentProperty.PropertyCode)})."); } _gridProxy = val2; _grid = component; GridGuid = text; TileCount = array.Length; val2 = null; Object.DestroyImmediate((Object)(object)val); val = null; Physics.SyncTransforms(); log?.Invoke($"Fish Warehouse native build Grid enabled (guid={GridGuid}, tiles={TileCount}, sourceTiles={tileCount}, rejectedTiles={rejectedTileCount}, ParentProperty={parentProperty.PropertyCode})."); return true; } catch (Exception ex) { log?.Invoke("Fish Warehouse native build Grid attachment failed safely: " + ex.Message); if (val3 != null) { try { FishWarehouseNativeGridPlacementPatches.Unregister(val3); parentProperty.Grids?.Remove(val3); } catch { } } if (val2 != null) { Object.DestroyImmediate((Object)(object)val2); } if (val != null) { Object.DestroyImmediate((Object)(object)val); } return false; } } public void Dispose() { if (_employeeHome != null) { try { _employeeHome.Destroy_Server(); } catch { } } _employeeHome = null; _employeeHomeComponent = null; if (_gridProxy != null) { try { FishWarehouseNativeGridPlacementPatches.Unregister(_grid); if (_grid != null && _grid.ParentProperty != null && _grid.ParentProperty.Grids != null) { _grid.ParentProperty.Grids.Remove(_grid); } } catch { } try { Object.DestroyImmediate((Object)(object)_gridProxy); } catch { } } _gridProxy = null; _grid = null; GridGuid = string.Empty; TileCount = 0; } private static Grid? FindLargestOwnedGrid(out int tileCount) { tileCount = 0; Grid result = null; Il2CppArrayBase val = Object.FindObjectsOfType(true); for (int i = 0; i < val.Length; i++) { Grid val2 = val[i]; Property val3 = ((val2 != null) ? val2.ParentProperty : null); if (val2 != null && ((Component)val2).gameObject != null && val3 != null && val3.IsOwned) { int length = ((Component)val2).GetComponentsInChildren(true).Length; if (length > tileCount) { result = val2; tileCount = length; } } } return result; } private static bool GridAlreadyUsesGuid(string gridGuid) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Il2CppArrayBase val = Object.FindObjectsOfType(true); for (int i = 0; i < val.Length; i++) { if (val[i] != null && ((object)val[i].GUID/*cast due to .constrained prefix*/).ToString().Equals(gridGuid, StringComparison.OrdinalIgnoreCase)) { return true; } } return false; } private static void PrepareTiles(Grid ownerGrid, Tile[] tiles) { foreach (Tile val in tiles) { if (val != null && ((Component)val).gameObject != null) { val.OwnerGrid = ownerGrid; ClearMember(val, "BuildableOccupants"); ClearMember(val, "OccupantTiles"); Renderer component = ((Component)val).GetComponent(); if (component != null) { component.enabled = false; } } } } private static void PositionGrid(Transform grid, Transform propertyRoot, Tile[] tiles) { //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) //IL_006a: 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) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_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_0037: 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_003e: 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) Bounds val = default(Bounds); ((Bounds)(ref val))..ctor(Vector3.zero, Vector3.zero); bool flag = false; foreach (Tile val2 in tiles) { if (val2 != null && ((Component)val2).transform != null) { Vector3 val3 = grid.InverseTransformPoint(((Component)val2).transform.position); if (!flag) { ((Bounds)(ref val))..ctor(val3, Vector3.zero); flag = true; } else { ((Bounds)(ref val)).Encapsulate(val3); } } } grid.SetParent(propertyRoot, false); grid.localRotation = Quaternion.identity; grid.localPosition = new Vector3(0f - ((Bounds)(ref val)).center.x, 0.02f - ((Bounds)(ref val)).center.y, 0f - ((Bounds)(ref val)).center.z); } private static void DisableNonTileColliders(GameObject root) { Il2CppArrayBase componentsInChildren = root.GetComponentsInChildren(true); for (int i = 0; i < componentsInChildren.Length; i++) { Collider val = componentsInChildren[i]; if (val != null && ((Component)val).GetComponent() == null) { val.enabled = false; } } } private static void RebuildNativeTopology(Grid grid, Tile[] tiles) { grid.Tiles.Clear(); grid.CoordinateTilePairs.Clear(); foreach (Tile val in tiles) { if (val != null) { val.OwnerGrid = grid; grid.RegisterTile(val); } } if (!Invoke(GetMember(grid, "_coordinateToTile"), "Clear") || !Invoke(grid, "ProcessCoordinateDataPairs") || !Invoke(grid, "SetGridSize")) { throw new InvalidOperationException("Native Grid coordinate topology could not be rebuilt."); } if (grid.Tiles.Count != tiles.Length) { throw new InvalidOperationException($"Native Grid tile registration mismatch (actual={grid.Tiles.Count}, expected={tiles.Length})."); } } private static object? GetMember(object target, string name) { Type type = target.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)property != null) { return property.GetValue(target); } return type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target); } private static bool TrySetMember(object target, string name, object value) { try { Type type = target.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)property != null && property.CanWrite) { property.SetValue(target, value); return true; } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)field == null) { return false; } field.SetValue(target, value); return true; } catch { return false; } } private static bool ClearMember(object target, string name) { object member = GetMember(target, name); if (member != null) { return Invoke(member, "Clear"); } return false; } private static bool Invoke(object? target, string name) { if (target == null) { return false; } try { MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if ((object)method == null) { return false; } method.Invoke(target, null); return true; } catch { return false; } } private static void AddBuildHoverCollider(Transform gridRoot, Tile[] tiles, float gridCellSize) { //IL_0044: 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_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_00cc: 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_00dc: 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_0103: 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_0122: 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_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_006e: 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_0075: 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) if (gridRoot == null) { throw new ArgumentNullException("gridRoot"); } if (tiles == null || tiles.Length == 0) { throw new ArgumentException("The measured Grid had no retained cells for its hover collider.", "tiles"); } if (!float.IsFinite(gridCellSize) || gridCellSize <= 0f) { throw new ArgumentOutOfRangeException("gridCellSize"); } bool flag = false; Bounds val = default(Bounds); foreach (Tile val2 in tiles) { if (val2 != null && ((Component)val2).transform != null) { Vector3 val3 = gridRoot.InverseTransformPoint(((Component)val2).transform.position); if (!flag) { ((Bounds)(ref val))..ctor(val3, Vector3.zero); flag = true; } else { ((Bounds)(ref val)).Encapsulate(val3); } } } if (!flag) { throw new InvalidOperationException("The measured Grid had no valid retained tile transforms."); } GameObject val4 = new GameObject("OC_FishWarehouse_BuildGridCollider"); val4.transform.SetParent(gridRoot, false); val4.layer = 7; val4.transform.localPosition = ((Bounds)(ref val)).center; val4.transform.localRotation = Quaternion.identity; val4.transform.localScale = Vector3.one; BoxCollider obj = val4.AddComponent(); ((Collider)obj).isTrigger = false; obj.center = Vector3.zero; obj.size = new Vector3(((Bounds)(ref val)).size.x + gridCellSize, 0.2f, ((Bounds)(ref val)).size.z + gridCellSize); } private static void RegisterPropertyGrid(Property parentProperty, Grid grid) { List grids = parentProperty.Grids; if (grids == null) { throw new InvalidOperationException("Fish Warehouse Property did not expose its native Grid list."); } if (!grids.Contains(grid)) { grids.Add(grid); } } private static Tile[] BuildMeasuredTileSet(Transform gridRoot, Transform propertyRoot, Grid ownerGrid, Tile[] tiles, float gridCellSize, out int rejectedTileCount) { //IL_008b: 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) //IL_0095: 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_00a8: 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_0189: 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_01b5: 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_01bf: Unknown result type (might be due to invalid IL or missing references) //IL_02fa: Unknown result type (might be due to invalid IL or missing references) if (gridRoot == null) { throw new ArgumentNullException("gridRoot"); } if (propertyRoot == null) { throw new ArgumentNullException("propertyRoot"); } if (ownerGrid == null) { throw new ArgumentNullException("ownerGrid"); } if (tiles == null || tiles.Length == 0) { throw new ArgumentException("The source Grid did not contain any Tile components.", "tiles"); } FishWarehouseBuildableFootprintDefinition fishWarehouseBuildableFootprintDefinition = FishWarehouseBuildableFootprintDefinition.CreateMeasured(gridCellSize); Tile val = ((IEnumerable)tiles).FirstOrDefault((Func)((Tile tile) => tile != null && ((Component)tile).transform != null && tile.x == 0 && tile.y == 0)); if (val == null || ((Component)val).transform == null) { throw new InvalidOperationException("The source Grid did not contain the existing (0,0) reference cell."); } Vector3 val2 = propertyRoot.InverseTransformPoint(((Component)val).transform.position); IReadOnlyList allowedCellCoordinates = fishWarehouseBuildableFootprintDefinition.GetAllowedCellCoordinates(val.x, val.y, val2.x, val2.z); if (allowedCellCoordinates.Count == 0) { throw new InvalidOperationException("The measured Fish Warehouse footprint produced no native cell coordinates."); } Dictionary<(int, int), Tile> dictionary = new Dictionary<(int, int), Tile>(); Vector3 val4 = default(Vector3); foreach (Tile val3 in tiles) { if (val3 != null && ((Component)val3).transform != null && ((Component)val3).gameObject != null) { if (!dictionary.TryAdd((val3.x, val3.y), val3)) { throw new InvalidOperationException($"The source Grid contained duplicate native cell ({val3.x},{val3.y})."); } ((Vector3)(ref val4))..ctor(val2.x + (float)(val3.x - val.x) * gridCellSize, val2.y, val2.z + (float)(val3.y - val.y) * gridCellSize); if (Vector3.Distance(propertyRoot.InverseTransformPoint(((Component)val3).transform.position), val4) > 0.02f) { throw new InvalidOperationException($"The source Grid coordinate spacing was not representable from the (0,0) cell ({val3.x},{val3.y})."); } } } List list = new List(allowedCellCoordinates.Count); HashSet<(int, int)> hashSet = new HashSet<(int, int)>(); for (int num2 = 0; num2 < allowedCellCoordinates.Count; num2++) { FishWarehouseGridCellCoordinate coordinate = allowedCellCoordinates[num2]; if (!hashSet.Add((coordinate.X, coordinate.Y))) { throw new InvalidOperationException($"The measured footprint produced duplicate native cell ({coordinate.X},{coordinate.Y})."); } if (dictionary.TryGetValue((coordinate.X, coordinate.Y), out var value)) { list.Add(value); continue; } Tile item = CreateGeneratedTile(val, gridRoot, ownerGrid, coordinate, val2, gridCellSize); list.Add(item); } rejectedTileCount = 0; foreach (Tile val5 in tiles) { if (val5 != null && ((Component)val5).transform != null && ((Component)val5).gameObject != null && !hashSet.Contains((val5.x, val5.y))) { ((Component)val5).gameObject.SetActive(false); rejectedTileCount++; } } return list.ToArray(); } private static Tile CreateGeneratedTile(Tile reference, Transform gridRoot, Grid ownerGrid, FishWarehouseGridCellCoordinate coordinate, Vector3 referenceLocal, float gridCellSize) { //IL_00da: 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_00e6: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) GameObject obj = Object.Instantiate(((Component)reference).gameObject, gridRoot, false); ((Object)obj).name = $"Grid [{coordinate.X},{coordinate.Y}]"; obj.SetActive(false); Tile component = obj.GetComponent(); if (component == null || ((Component)component).transform == null) { throw new InvalidOperationException($"Generated native Grid cell ({coordinate.X},{coordinate.Y}) did not retain its Tile component."); } Vector3 localPosition = gridRoot.InverseTransformPoint(gridRoot.parent.TransformPoint(referenceLocal)); localPosition.x += (float)(coordinate.X - reference.x) * gridCellSize; localPosition.z += (float)(coordinate.Y - reference.y) * gridCellSize; ((Component)component).transform.localPosition = localPosition; component.InitializePropertyTile(coordinate.X, coordinate.Y, reference.AvailableOffset, ownerGrid); component.OwnerGrid = ownerGrid; ClearMember(component, "BuildableOccupants"); ClearMember(component, "OccupantTiles"); Renderer component2 = ((Component)component).GetComponent(); if (component2 != null) { component2.enabled = false; } obj.SetActive(true); return component; } } public enum FishWarehouseContinuousNavigationState { Inactive, Settling, Ready, Failed } public interface IFishWarehouseContinuousNavigationActions { bool TryPreflightGeometry(); bool TryActivateRoom(); bool TryOpenGarage(); string GetBuildableAndConfigurableSignature(); FishWarehouseNativeNavigationBuildResult TryBuildNavigation(); bool ValidateNavigation(); bool RemoveNavigation(); void RestoreGarage(); void DisposeRoom(); } public sealed class FishWarehouseContinuousNavigationCoordinator { public const float StableSettleSeconds = 0.5f; public const float SettleTimeoutSeconds = 5f; private readonly IFishWarehouseContinuousNavigationActions _actions; private string? _lastSignature; private float _startedAt; private float _signatureStableSince; private bool _rollbackRequired; private bool _rolledBack; private bool _hasPostStartTick; public FishWarehouseContinuousNavigationState State { get; private set; } public bool HasPendingCleanup { get { if (_rollbackRequired) { return !_rolledBack; } return false; } } public FishWarehouseContinuousNavigationCoordinator(IFishWarehouseContinuousNavigationActions actions) { _actions = actions ?? throw new ArgumentNullException("actions"); } public void Start(float now) { if (State == FishWarehouseContinuousNavigationState.Inactive) { ResetRun(now); if (!_actions.TryPreflightGeometry()) { State = FishWarehouseContinuousNavigationState.Failed; return; } if (!_actions.TryActivateRoom() || !_actions.TryOpenGarage()) { FailAfterMutation(); return; } _rollbackRequired = true; _lastSignature = _actions.GetBuildableAndConfigurableSignature(); _signatureStableSince = now; State = FishWarehouseContinuousNavigationState.Settling; } } public void Tick(float now) { if (State != FishWarehouseContinuousNavigationState.Settling) { return; } if (!_hasPostStartTick) { _hasPostStartTick = true; if (now - _startedAt > 5f) { _startedAt = now; _signatureStableSince = now; } } if (now - _startedAt >= 5f) { FailAfterMutation(); return; } string buildableAndConfigurableSignature = _actions.GetBuildableAndConfigurableSignature(); if (!string.Equals(buildableAndConfigurableSignature, _lastSignature, StringComparison.Ordinal)) { _lastSignature = buildableAndConfigurableSignature; _signatureStableSince = now; } else { if (now - _signatureStableSince < 0.5f) { return; } switch (_actions.TryBuildNavigation()) { case FishWarehouseNativeNavigationBuildResult.Pending: break; case FishWarehouseNativeNavigationBuildResult.Succeeded: if (_actions.ValidateNavigation()) { State = FishWarehouseContinuousNavigationState.Ready; break; } goto default; default: FailAfterMutation(); break; } } } public bool Stop() { if (!_rollbackRequired) { return true; } if (!Rollback()) { return false; } if (State != FishWarehouseContinuousNavigationState.Failed) { State = FishWarehouseContinuousNavigationState.Inactive; } return true; } private void FailAfterMutation() { _rollbackRequired = true; Rollback(); State = FishWarehouseContinuousNavigationState.Failed; } private void ResetRun(float now) { _lastSignature = null; _startedAt = now; _signatureStableSince = now; _rollbackRequired = false; _rolledBack = false; _hasPostStartTick = false; } private bool Rollback() { if (_rolledBack) { return true; } if (!_actions.RemoveNavigation()) { return false; } _actions.RestoreGarage(); _actions.DisposeRoom(); _rolledBack = true; return true; } } public enum FishWarehouseDeliveryActionKind { Dispatch, Status, StatusDisplay } public sealed record FishWarehousePendingDelivery(string DeliveryId, TDelivery Delivery, FishWarehouseDeliveryActionKind ActionKind = FishWarehouseDeliveryActionKind.Dispatch) where TDelivery : class; public sealed class FishWarehouseDeliveryRestoreGate where TDelivery : class { private readonly record struct DeliveryActionKey(string DeliveryId, FishWarehouseDeliveryActionKind ActionKind); private readonly string _targetPropertyCode; private readonly Dictionary _pending = new Dictionary(); private readonly Dictionary _sequence = new Dictionary(); private readonly HashSet _attempted = new HashSet(); private readonly HashSet _replayed = new HashSet(); private long _nextSequence; public int PendingCount => _pending.Count; public FishWarehouseDeliveryRestoreGate(string targetPropertyCode) { _targetPropertyCode = targetPropertyCode; } public bool ShouldSuppress(TDelivery delivery, string destinationCode, string deliveryId, FishWarehouseDeliveryActionKind actionKind, bool targetReady, bool isLoading) { if (!string.Equals(destinationCode, _targetPropertyCode, StringComparison.Ordinal)) { return false; } if (string.IsNullOrWhiteSpace(deliveryId)) { throw new ArgumentException("Fish Warehouse delivery identity is required.", "deliveryId"); } DeliveryActionKey deliveryActionKey = new DeliveryActionKey(deliveryId, actionKind); if (actionKind != FishWarehouseDeliveryActionKind.Status && _replayed.Contains(deliveryActionKey)) { return true; } if (!ShouldDefer(actionKind, targetReady, isLoading)) { return false; } if (_pending.TryAdd(deliveryActionKey, delivery)) { _sequence[deliveryActionKey] = _nextSequence++; } return true; } public bool ShouldSuppress(TDelivery delivery, string destinationCode, string deliveryId, bool targetReady, bool isRestoreContext) { if (!isRestoreContext) { return false; } return ShouldSuppress(delivery, destinationCode, deliveryId, FishWarehouseDeliveryActionKind.Dispatch, targetReady, isLoading: false); } public IReadOnlyList> BeginReadyReplay(bool targetReady) { if (!targetReady) { return Array.Empty>(); } return (from entry in _pending where _attempted.Add(entry.Key) orderby ActionOrder(entry.Key.ActionKind), _sequence[entry.Key] select new FishWarehousePendingDelivery(entry.Key.DeliveryId, entry.Value, entry.Key.ActionKind)).ToArray(); } public void MarkReplayed(string deliveryId, FishWarehouseDeliveryActionKind actionKind) { DeliveryActionKey deliveryActionKey = new DeliveryActionKey(deliveryId, actionKind); _pending.Remove(deliveryActionKey); _sequence.Remove(deliveryActionKey); _attempted.Remove(deliveryActionKey); _replayed.Add(deliveryActionKey); } public void MarkReplayed(string deliveryId) { MarkReplayed(deliveryId, FishWarehouseDeliveryActionKind.Dispatch); } public bool HasPending(string deliveryId) { return _pending.Keys.Any((DeliveryActionKey key) => string.Equals(key.DeliveryId, deliveryId, StringComparison.Ordinal)); } public bool HasPending(string deliveryId, FishWarehouseDeliveryActionKind actionKind) { return _pending.ContainsKey(new DeliveryActionKey(deliveryId, actionKind)); } public bool IsReplayed(string deliveryId, FishWarehouseDeliveryActionKind actionKind) { return _replayed.Contains(new DeliveryActionKey(deliveryId, actionKind)); } public void Reset() { _pending.Clear(); _sequence.Clear(); _attempted.Clear(); _replayed.Clear(); _nextSequence = 0L; } private static bool ShouldDefer(FishWarehouseDeliveryActionKind actionKind, bool targetReady, bool isLoading) { return actionKind switch { FishWarehouseDeliveryActionKind.Dispatch => !targetReady, FishWarehouseDeliveryActionKind.Status => isLoading && !targetReady, FishWarehouseDeliveryActionKind.StatusDisplay => isLoading && !targetReady, _ => throw new ArgumentOutOfRangeException("actionKind", actionKind, null), }; } private static int ActionOrder(FishWarehouseDeliveryActionKind actionKind) { return actionKind switch { FishWarehouseDeliveryActionKind.Dispatch => 0, FishWarehouseDeliveryActionKind.Status => 1, FishWarehouseDeliveryActionKind.StatusDisplay => 2, _ => throw new ArgumentOutOfRangeException("actionKind", actionKind, null), }; } } public sealed record FishWarehouseDeliveryRestoreFlushResult(int PendingCount, string? Failure) { public string? FailureReason => Failure; } internal static class FishWarehouseDeliveryRestorePatch { private abstract record PendingDeliveryPayload; private sealed record PendingDispatch(DeliveryManager Manager, DeliveryInstance Delivery) : PendingDeliveryPayload(); private sealed record PendingStatus(DeliveryInstance Delivery, EDeliveryStatus Status) : PendingDeliveryPayload() { [CompilerGenerated] public void Deconstruct(out DeliveryInstance Delivery, out EDeliveryStatus Status) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected I4, but got Unknown Delivery = this.Delivery; Status = (EDeliveryStatus)(int)this.Status; } } private sealed record PendingStatusDisplay(DeliveryApp App, DeliveryInstance Delivery) : PendingDeliveryPayload(); private static readonly FishWarehouseDeliveryRestoreGate Gate = new FishWarehouseDeliveryRestoreGate("oc_fishwarehouse"); private static Func _isReady = () => false; private static Action _log = delegate { }; private static bool _replaying; private static string? _statusReplayDeliveryId; private static bool _nestedStatusDisplayObserved; private static bool _replayFailureReported; public static FishWarehouseDeliveryRestoreFlushResult LastFlushResult { get; private set; } = new FishWarehouseDeliveryRestoreFlushResult(0, null); public static void Apply(Harmony harmony, Func isReady, Action? log = null) { _isReady = isReady; _log = log ?? ((Action)delegate { }); MethodInfo methodInfo = AccessTools.Method(typeof(DeliveryManager), "SendDelivery", new Type[1] { typeof(DeliveryInstance) }, (Type[])null) ?? throw new MissingMethodException(typeof(DeliveryManager).FullName, "SendDelivery"); MethodInfo methodInfo2 = AccessTools.Method(typeof(DeliveryInstance), "SetStatus", new Type[1] { typeof(EDeliveryStatus) }, (Type[])null) ?? throw new MissingMethodException(typeof(DeliveryInstance).FullName, "SetStatus"); MethodInfo methodInfo3 = AccessTools.Method(typeof(DeliveryApp), "CreateDeliveryStatusDisplay", new Type[1] { typeof(DeliveryInstance) }, (Type[])null) ?? throw new MissingMethodException(typeof(DeliveryApp).FullName, "CreateDeliveryStatusDisplay"); harmony.Patch((MethodBase)methodInfo, HarmonyMethod("DispatchPrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, HarmonyMethod("StatusPrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo3, HarmonyMethod("StatusDisplayPrefix"), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static HarmonyMethod HarmonyMethod(string methodName) { //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown return new HarmonyMethod(AccessTools.Method(typeof(FishWarehouseDeliveryRestorePatch), methodName, (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(FishWarehouseDeliveryRestorePatch).FullName, methodName)); } private static bool DispatchPrefix(DeliveryManager __instance, DeliveryInstance __0) { if (_replaying || __0 == null) { return true; } return ApplyGate(new PendingDispatch(__instance, __0), __0, FishWarehouseDeliveryActionKind.Dispatch, IsLoading()); } private static bool StatusPrefix(DeliveryInstance __instance, EDeliveryStatus __0) { //IL_000d: Unknown result type (might be due to invalid IL or missing references) if (_replaying || __instance == null) { return true; } return ApplyGate(new PendingStatus(__instance, __0), __instance, FishWarehouseDeliveryActionKind.Status, IsLoading()); } private static bool StatusDisplayPrefix(DeliveryApp __instance, DeliveryInstance __0) { if (__0 == null) { return true; } if (_replaying) { if (string.Equals(_statusReplayDeliveryId, __0.DeliveryID, StringComparison.Ordinal) && Gate.HasPending(__0.DeliveryID ?? string.Empty, FishWarehouseDeliveryActionKind.StatusDisplay)) { _nestedStatusDisplayObserved = true; } return true; } return ApplyGate(new PendingStatusDisplay(__instance, __0), __0, FishWarehouseDeliveryActionKind.StatusDisplay, IsLoading()); } private static bool ApplyGate(PendingDeliveryPayload payload, DeliveryInstance delivery, FishWarehouseDeliveryActionKind actionKind, bool isLoading) { try { string text = delivery.DeliveryID ?? string.Empty; bool num = Gate.ShouldSuppress(payload, delivery.DestinationCode ?? string.Empty, text, actionKind, _isReady(), isLoading); if (num) { _log($"Fish Warehouse delivery action deferred (delivery={text}, action={actionKind})."); } return !num; } catch (Exception ex) { _log("Fish Warehouse saved-delivery gate failed safely; vanilla action will continue: " + ex.Message); return true; } } internal static void ReplayUntilFailure(IEnumerable> pendingActions, Action> replay, Action, Exception> onFailure) where TDelivery : class { foreach (FishWarehousePendingDelivery pendingAction in pendingActions) { try { replay(pendingAction); } catch (Exception arg) { onFailure(pendingAction, arg); break; } } } public static FishWarehouseDeliveryRestoreFlushResult FlushIfReady() { IReadOnlyList> ready = Gate.BeginReadyReplay(_isReady()); string failure = null; ReplayUntilFailure(ready.Where((FishWarehousePendingDelivery pending) => !Gate.IsReplayed(pending.DeliveryId, pending.ActionKind)), delegate(FishWarehousePendingDelivery pending) { try { _replaying = true; Replay(pending, ready); } finally { _statusReplayDeliveryId = null; _nestedStatusDisplayObserved = false; _replaying = false; } }, delegate(FishWarehousePendingDelivery pending, Exception ex) { if (failure == null) { failure = $"delivery={pending.DeliveryId}, action={pending.ActionKind}: {ex.Message}"; } if (!_replayFailureReported) { _replayFailureReported = true; _log("Fish Warehouse saved delivery action remains pending after replay failure (" + failure + ")."); } }); return StoreFlushResult(new FishWarehouseDeliveryRestoreFlushResult(Gate.PendingCount, failure)); } private static void Replay(FishWarehousePendingDelivery pending, IReadOnlyList> ready) { //IL_00c5: Unknown result type (might be due to invalid IL or missing references) switch (pending.ActionKind) { case FishWarehouseDeliveryActionKind.Dispatch: { PendingDispatch pendingDispatch = (PendingDispatch)pending.Delivery; pendingDispatch.Manager.SendDelivery(pendingDispatch.Delivery); Gate.MarkReplayed(pending.DeliveryId, pending.ActionKind); _log("Fish Warehouse delivery action replayed (delivery=" + pending.DeliveryId + ", action=Dispatch)."); break; } case FishWarehouseDeliveryActionKind.Status: { PendingStatus pendingStatus = (PendingStatus)pending.Delivery; _statusReplayDeliveryId = pending.DeliveryId; _nestedStatusDisplayObserved = false; pendingStatus.Delivery.SetStatus(pendingStatus.Status); Gate.MarkReplayed(pending.DeliveryId, pending.ActionKind); FishWarehousePendingDelivery fishWarehousePendingDelivery = ready.FirstOrDefault((FishWarehousePendingDelivery candidate) => candidate.DeliveryId == pending.DeliveryId && candidate.ActionKind == FishWarehouseDeliveryActionKind.StatusDisplay); if ((object)fishWarehousePendingDelivery != null && Gate.HasPending(pending.DeliveryId, FishWarehouseDeliveryActionKind.StatusDisplay)) { if (_nestedStatusDisplayObserved) { Gate.MarkReplayed(pending.DeliveryId, FishWarehouseDeliveryActionKind.StatusDisplay); _log("Fish Warehouse delivery action replayed (delivery=" + pending.DeliveryId + ", action=StatusDisplay, mode=nested)."); } else { ReplayStatusDisplay(fishWarehousePendingDelivery); } } _log("Fish Warehouse delivery action replayed (delivery=" + pending.DeliveryId + ", action=Status)."); break; } case FishWarehouseDeliveryActionKind.StatusDisplay: ReplayStatusDisplay(pending); break; default: throw new ArgumentOutOfRangeException("ActionKind", pending.ActionKind, null); } } private static void ReplayStatusDisplay(FishWarehousePendingDelivery pending) { PendingStatusDisplay pendingStatusDisplay = (PendingStatusDisplay)pending.Delivery; pendingStatusDisplay.App.CreateDeliveryStatusDisplay(pendingStatusDisplay.Delivery); Gate.MarkReplayed(pending.DeliveryId, pending.ActionKind); _log("Fish Warehouse delivery action replayed (delivery=" + pending.DeliveryId + ", action=StatusDisplay, mode=explicit)."); } internal static FishWarehouseDeliveryRestoreFlushResult StoreFlushResult(FishWarehouseDeliveryRestoreFlushResult result) { if (result.Failure == null && LastFlushResult.Failure != null) { return LastFlushResult; } LastFlushResult = result; return result; } public static void Reset() { Gate.Reset(); _isReady = () => false; _log = delegate { }; _replaying = false; _statusReplayDeliveryId = null; _nestedStatusDisplayObserved = false; _replayFailureReported = false; LastFlushResult = new FishWarehouseDeliveryRestoreFlushResult(0, null); } private static bool IsLoading() { LoadManager instance = Singleton.Instance; if (instance == null) { return false; } return instance.IsLoading; } } public sealed class FishWarehouseDockDeliveryBridgeHost { private readonly FishWarehouseDockDeliveryPopulationLatch _populationLatch = new FishWarehouseDockDeliveryPopulationLatch(); public void Tick(Property property, bool hostAuthority, Action? log = null) { //IL_009e: Unknown result type (might be due to invalid IL or missing references) //IL_00a4: Invalid comparison between Unknown and I4 if (!hostAuthority || property == null || (Object)(object)property == (Object)null) { return; } try { DeliveryInstance val = ResolveActiveDelivery(property); if (val == null) { _populationLatch.ObserveActiveDelivery(null); return; } string text = SafeDeliveryId(val); _populationLatch.ObserveActiveDelivery(text); if (string.IsNullOrWhiteSpace(text) || _populationLatch.HasPopulated(text)) { return; } LoadingDock val2 = SafeDock(val); if (val2 == null || HasOccupant(val2)) { return; } TryRefresh(val2); if (HasOccupant(val2)) { _populationLatch.MarkPopulated(text); log?.Invoke("[oc7-bridge] dock populated via native RefreshOccupant."); } else { if ((int)SafeStatus(val) != 2) { return; } LandVehicle val3 = SafeVan(val); if (val3 != null && !((Object)(object)val3 == (Object)null)) { val2.SetOccupant(val3); if (HasOccupant(val2)) { _populationLatch.MarkPopulated(text); log?.Invoke("[oc7-bridge] dock populated via direct SetOccupant (arrived delivery van)."); } } } } catch { } } private static DeliveryInstance? ResolveActiveDelivery(Property property) { try { if (!NetworkSingleton.InstanceExists) { return null; } DeliveryInstance delivery = NetworkSingleton.Instance.GetDelivery(property); return (delivery != null && delivery != null) ? delivery : null; } catch { return null; } } private static LoadingDock? SafeDock(DeliveryInstance delivery) { try { LoadingDock loadingDock = delivery.LoadingDock; return (loadingDock != null && (Object)(object)loadingDock != (Object)null) ? loadingDock : null; } catch { return null; } } private static string SafeDeliveryId(DeliveryInstance delivery) { try { return delivery.DeliveryID ?? string.Empty; } catch { return string.Empty; } } private static LandVehicle? SafeVan(DeliveryInstance delivery) { try { DeliveryVehicle activeVehicle = delivery.ActiveVehicle; if (activeVehicle == null || (Object)(object)activeVehicle == (Object)null) { return null; } LandVehicle vehicle = activeVehicle.Vehicle; return (vehicle != null && (Object)(object)vehicle != (Object)null) ? vehicle : null; } catch { return null; } } private static EDeliveryStatus SafeStatus(DeliveryInstance delivery) { //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_000b: 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) try { return delivery.Status; } catch { return (EDeliveryStatus)0; } } private static bool HasOccupant(LoadingDock dock) { try { LandVehicle dynamicOccupant = dock.DynamicOccupant; return dynamicOccupant != null && (Object)(object)dynamicOccupant != (Object)null; } catch { return true; } } private static void TryRefresh(LoadingDock dock) { try { dock.RefreshOccupant(); } catch { } } } public sealed class FishWarehouseDockDeliveryPopulationLatch { private string? _populatedDeliveryId; public bool HasPopulated(string deliveryId) { if (!string.IsNullOrWhiteSpace(deliveryId)) { return string.Equals(_populatedDeliveryId, deliveryId, StringComparison.Ordinal); } return false; } public void MarkPopulated(string deliveryId) { if (!string.IsNullOrWhiteSpace(deliveryId)) { _populatedDeliveryId = deliveryId; } } public void ObserveActiveDelivery(string? deliveryId) { if (!string.Equals(_populatedDeliveryId, deliveryId, StringComparison.Ordinal)) { _populatedDeliveryId = null; } } public void Reset() { _populatedDeliveryId = null; } } internal static class FishWarehouseDockOccupantRefreshPatch { public static void Apply(Harmony harmony) { //IL_007d: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(LoadingDock), "SetOccupant", new Type[1] { typeof(LandVehicle) }, (Type[])null); if ((object)methodInfo == null) { throw new MissingMethodException(typeof(LoadingDock).FullName, "SetOccupant"); } MethodInfo methodInfo2 = AccessTools.Method(typeof(FishWarehouseDockOccupantRefreshPatch), "Prefix", (Type[])null, (Type[])null); if ((object)methodInfo2 == null) { throw new MissingMethodException(typeof(FishWarehouseDockOccupantRefreshPatch).FullName, "Prefix"); } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool Prefix(LoadingDock __instance, LandVehicle? __0) { try { if (__0 != null && (Object)(object)__0 != (Object)null) { return true; } return !FishWarehouseDockOccupantRefreshPolicy.ShouldSuppressOccupantClear(IsFishWarehouseDock(__instance), HasDynamicOccupant(__instance), HasOutputItems(__instance)); } catch { return true; } } private static bool IsFishWarehouseDock(LoadingDock dock) { //IL_0008: 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) string guid = ((object)dock.GUID/*cast due to .constrained prefix*/).ToString(); return FishWarehouseLoadingDockDefinition.Docks.Any((FishWarehouseDockDefinition definition) => string.Equals(definition.DockGuid, guid, StringComparison.OrdinalIgnoreCase)); } private static bool HasDynamicOccupant(LoadingDock dock) { LandVehicle dynamicOccupant = dock.DynamicOccupant; if (dynamicOccupant != null) { return (Object)(object)dynamicOccupant != (Object)null; } return false; } private static bool HasOutputItems(LoadingDock dock) { Enumerator enumerator = dock.OutputSlots.GetEnumerator(); while (enumerator.MoveNext()) { ItemSlot current = enumerator.Current; if (current.ItemInstance != null && current.ItemInstance != null && current.Quantity > 0) { return true; } } return false; } } internal static class FishWarehouseDockOccupantRefreshPolicy { public static bool ShouldSuppressOccupantClear(bool isFishWarehouseDock, bool hasDynamicOccupant, bool hasOutputItems) { return isFishWarehouseDock && hasDynamicOccupant && hasOutputItems; } } public sealed class FishWarehouseEmployeeInfrastructureHost { private GameObject? _root; public bool IsReady => _root != null; public bool TryAttach(Property property, Action? log = null) { //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Expected O, but got Unknown if (IsReady) { return true; } if (property == null) { return false; } GameObject val = null; try { if (!FishWarehouseEmployeeInfrastructureDefinition.HasValidLayout()) { throw new InvalidOperationException("Fish Warehouse employee point definition failed its bounds/capacity contract."); } val = new GameObject("OC_FishWarehouse_EmployeeInfrastructure"); val.transform.SetParent(((Component)property).transform, false); Transform val2 = CreatePoint(val.transform, FishWarehouseEmployeeInfrastructureDefinition.NpcSpawnPoint); Il2CppReferenceArray val3 = new Il2CppReferenceArray(10L); for (int i = 0; i < FishWarehouseEmployeeInfrastructureDefinition.IdlePoints.Count; i++) { ((Il2CppArrayBase)(object)val3)[i] = CreatePoint(val.transform, FishWarehouseEmployeeInfrastructureDefinition.IdlePoints[i]); } property.EmployeeCapacity = 10; property.NPCSpawnPoint = val2; property.EmployeeIdlePoints = val3; if (property.EmployeeCapacity != 10 || (Object)(object)property.NPCSpawnPoint != (Object)(object)val2 || property.EmployeeIdlePoints == null || ((Il2CppArrayBase)(object)property.EmployeeIdlePoints).Length != 10) { throw new InvalidOperationException("Fish Warehouse Property did not retain employee infrastructure references."); } _root = val; val = null; log?.Invoke($"Fish Warehouse employee infrastructure enabled (capacity={property.EmployeeCapacity}, idlePoints={((Il2CppArrayBase)(object)property.EmployeeIdlePoints).Length}, npcSpawn={((Object)property.NPCSpawnPoint).name})."); return true; } catch (Exception ex) { if (val != null) { Object.DestroyImmediate((Object)(object)val); } log?.Invoke("Fish Warehouse employee infrastructure failed safely: " + ex.Message); return false; } } public void Dispose() { if (_root != null) { Object.DestroyImmediate((Object)(object)_root); } _root = null; } private static Transform CreatePoint(Transform parent, FishWarehouseEmployeePointDefinition definition) { //IL_0006: 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_0036: Unknown result type (might be due to invalid IL or missing references) Transform transform = new GameObject(definition.Name).transform; transform.SetParent(parent, false); transform.localPosition = new Vector3(definition.X, definition.Y, definition.Z); transform.localRotation = Quaternion.identity; return transform; } } public enum FishWarehouseEmployeeReplayState { Pending, Succeeded, Failed } public sealed record FishWarehouseEmployeeReplayResult(FishWarehouseEmployeeReplayState State, int CapturedEmployeeCount, int SupportedCapturedEmployeeCount, int ReplayedEmployeeCount, IReadOnlyList UnsupportedEmployees, bool CanAdvanceEmployeesReplayed, string? PrimaryFailureReason, string? FailureReason, IReadOnlyList? InventoryRestoredEmployeeGuids = null); public sealed class FishWarehouseEmployeeReplayHost { private enum ReplayStage { NotStarted, PrimaryVerification, FallbackVerification } private sealed class Entry { public FishWarehouseEmployeeReplayDescriptor Descriptor { get; } public FishWarehouseEmployeeReplayExpectedState? Expected { get; set; } public ReplayStage Stage { get; set; } public bool GuidWasRegistered { get; set; } public bool FallbackAttempted { get; set; } public bool IsReplayed { get; set; } public bool IsUnsupported { get; set; } public float PrimaryStartedAt { get; set; } public float FallbackStartedAt { get; set; } public string? PrimaryFailureReason { get; set; } public string? LastVerificationReason { get; set; } public Entry(FishWarehouseEmployeeReplayDescriptor descriptor) { Descriptor = descriptor; } } public const float ConfigurationVerificationTimeoutSeconds = 3f; private readonly IFishWarehouseNativeEmployeeReplayAdapter _adapter; private readonly IReadOnlyList _entries; private readonly Dictionary _unsupportedByGuid; private readonly Func _realtimeSinceStartup; private readonly Action _log; public FishWarehouseEmployeeReplayResult Result { get; private set; } public FishWarehouseEmployeeReplayHost(IFishWarehouseNativeEmployeeReplayAdapter adapter, IReadOnlyList capturedEmployees, IReadOnlyList existingUnsupportedEmployees, Func? realtimeSinceStartup = null, Action? log = null) { _adapter = adapter ?? throw new ArgumentNullException("adapter"); _entries = (capturedEmployees ?? throw new ArgumentNullException("capturedEmployees")).Select((FishWarehouseEmployeeReplayDescriptor descriptor) => new Entry(descriptor)).ToArray(); _unsupportedByGuid = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (FishWarehouseUnsupportedEmployeeRecord item in existingUnsupportedEmployees ?? throw new ArgumentNullException("existingUnsupportedEmployees")) { _unsupportedByGuid.TryAdd(item.Guid, item); } _realtimeSinceStartup = realtimeSinceStartup ?? ((Func)(() => Time.realtimeSinceStartup)); _log = log ?? ((Action)delegate { }); Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); } public FishWarehouseEmployeeReplayResult Replay(bool navigationReady) { FishWarehouseEmployeeReplayState state = Result.State; if ((uint)(state - 1) <= 1u) { return Result; } PreserveUnsupportedRecords(); if (!navigationReady) { return Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); } foreach (Entry entry in _entries) { if (entry.IsUnsupported || entry.IsReplayed) { continue; } if (!entry.Descriptor.IsSupportedPackager) { Preserve(entry, "unsupported employee type"); continue; } if ((object)entry.Expected == null) { if (!entry.Descriptor.TryParseExpectedState(out FishWarehouseEmployeeReplayExpectedState expected, out string failureReason)) { Preserve(entry, "malformed supported employee payload: " + failureReason); return Fail("malformed-employee-payload (guid=" + entry.Descriptor.Guid + "): " + failureReason); } entry.Expected = expected; } if (Process(entry, entry.Expected)) { continue; } return Result; } return Result = BuildResult(FishWarehouseEmployeeReplayState.Succeeded, null); } private bool Process(Entry entry, FishWarehouseEmployeeReplayExpectedState expected) { float num = _realtimeSinceStartup(); if (entry.Stage == ReplayStage.NotStarted) { if (!_adapter.TryIsGuidRegistered(entry.Descriptor.Guid, out bool isRegistered, out string failureReason)) { Preserve(entry, "GUID interop rejected the saved GUID"); Fail("guid-interop-failure (guid=" + entry.Descriptor.Guid + "): " + failureReason); return false; } entry.GuidWasRegistered = isRegistered; entry.PrimaryStartedAt = num; if (isRegistered) { entry.Stage = ReplayStage.PrimaryVerification; } else { string failureReason2; switch (_adapter.TryLoadPrimary(entry.Descriptor, out failureReason2)) { case FishWarehouseEmployeePrimaryLoadResult.Started: break; case FishWarehouseEmployeePrimaryLoadResult.LoaderUnavailable: Preserve(entry, "registry loader was unavailable"); return true; case FishWarehouseEmployeePrimaryLoadResult.Failed: entry.PrimaryFailureReason = "primary-failure (guid=" + entry.Descriptor.Guid + "): " + failureReason2; return StartFallback(entry, expected, num); default: throw new InvalidOperationException("Unknown primary replay result."); } entry.Stage = ReplayStage.PrimaryVerification; } } if (entry.Stage == ReplayStage.PrimaryVerification) { if (!TryRestoreActiveMove(entry, expected, out string failureReason3)) { if (failureReason3 != null) { entry.LastVerificationReason = failureReason3; } if (_realtimeSinceStartup() < entry.PrimaryStartedAt + 3f) { return false; } } string reason; switch (TryVerify(entry, expected, out reason)) { case FishWarehouseEmployeeObservationResult.Ready: entry.IsReplayed = true; return true; case FishWarehouseEmployeeObservationResult.Failed: entry.PrimaryFailureReason = "primary-observation-failure (guid=" + entry.Descriptor.Guid + "): " + reason; if (!entry.GuidWasRegistered) { return StartFallback(entry, expected, num); } return StartRegisteredConfiguration(entry, expected, num); default: if (num < entry.PrimaryStartedAt + 3f) { entry.LastVerificationReason = reason; Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); return false; } entry.PrimaryFailureReason = "primary-timeout (guid=" + entry.Descriptor.Guid + "): " + reason; if (!entry.GuidWasRegistered) { return StartFallback(entry, expected, num); } return StartRegisteredConfiguration(entry, expected, num); } } if (entry.Stage == ReplayStage.FallbackVerification) { string failureReason4; FishWarehouseEmployeeFallbackConfigurationResult fishWarehouseEmployeeFallbackConfigurationResult = _adapter.TryConfigureFallback(entry.Descriptor, expected, out failureReason4); if (fishWarehouseEmployeeFallbackConfigurationResult == FishWarehouseEmployeeFallbackConfigurationResult.Failed) { Preserve(entry, "fallback configuration failed"); Fail("fallback-configuration-failure (guid=" + entry.Descriptor.Guid + "): " + failureReason4); return false; } if (!TryRestoreActiveMove(entry, expected, out string failureReason5)) { if (failureReason5 != null) { entry.LastVerificationReason = failureReason5; } Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); return false; } string reason2 = null; if (fishWarehouseEmployeeFallbackConfigurationResult == FishWarehouseEmployeeFallbackConfigurationResult.Configured && TryVerify(entry, expected, out reason2) == FishWarehouseEmployeeObservationResult.Ready) { entry.IsReplayed = true; return true; } if (num < entry.FallbackStartedAt + 3f) { entry.LastVerificationReason = ((fishWarehouseEmployeeFallbackConfigurationResult == FishWarehouseEmployeeFallbackConfigurationResult.Pending) ? "fallback configuration pending" : reason2); Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); return false; } Preserve(entry, "fallback configuration verification timed out"); Fail("fallback-configuration-timeout (guid=" + entry.Descriptor.Guid + "): " + (entry.LastVerificationReason ?? "unavailable")); return false; } throw new InvalidOperationException("Unknown employee replay stage."); } private bool TryRestoreActiveMove(Entry entry, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason) { switch (_adapter.TryRestoreActiveMove(entry.Descriptor, expected, out failureReason)) { case FishWarehouseEmployeeActiveMoveRestoreResult.Failed: Fail("active-move-restore-failure (guid=" + entry.Descriptor.Guid + "): " + failureReason); return false; case FishWarehouseEmployeeActiveMoveRestoreResult.Pending: Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); return false; default: return true; } } private bool StartFallback(Entry entry, FishWarehouseEmployeeReplayExpectedState expected, float now) { if (!_adapter.TryIsGuidRegistered(entry.Descriptor.Guid, out bool isRegistered, out string failureReason)) { Fail("fallback-guid-adoption-check-failed (guid=" + entry.Descriptor.Guid + "): " + failureReason); return false; } if (isRegistered) { return StartRegisteredConfiguration(entry, expected, now); } if (entry.FallbackAttempted) { Fail("fallback-retry-rejected (guid=" + entry.Descriptor.Guid + ")"); return false; } entry.FallbackAttempted = true; if (_adapter.TryCreateFallback(entry.Descriptor, expected, out string failureReason2) == FishWarehouseEmployeeFallbackCreationResult.Failed) { Preserve(entry, "fallback creation failed"); Fail("fallback-creation-failure (guid=" + entry.Descriptor.Guid + "): " + failureReason2); return false; } entry.Stage = ReplayStage.FallbackVerification; entry.FallbackStartedAt = now; Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); return false; } private bool StartRegisteredConfiguration(Entry entry, FishWarehouseEmployeeReplayExpectedState expected, float now) { entry.Stage = ReplayStage.FallbackVerification; entry.FallbackStartedAt = now; entry.LastVerificationReason = entry.PrimaryFailureReason; Result = BuildResult(FishWarehouseEmployeeReplayState.Pending, null); return false; } private FishWarehouseEmployeeObservationResult TryVerify(Entry entry, FishWarehouseEmployeeReplayExpectedState expected, out string? reason) { FishWarehouseEmployeeReplayObservedState observation; FishWarehouseEmployeeObservationResult fishWarehouseEmployeeObservationResult = _adapter.TryObserveEmployee(entry.Descriptor.Guid, out observation, out reason); if (fishWarehouseEmployeeObservationResult != FishWarehouseEmployeeObservationResult.Ready || (object)observation == null) { return fishWarehouseEmployeeObservationResult; } if (!observation.IsAlive) { reason = "employee-not-alive"; return FishWarehouseEmployeeObservationResult.Pending; } reason = Compare(expected, observation); if (reason != null) { return FishWarehouseEmployeeObservationResult.Pending; } return FishWarehouseEmployeeObservationResult.Ready; } private static string? Compare(FishWarehouseEmployeeReplayExpectedState expected, FishWarehouseEmployeeReplayObservedState actual) { if (!SameGuid(expected.Guid, actual.Guid)) { return "guid-mismatch"; } if (!string.Equals(expected.Identity, actual.Identity, StringComparison.Ordinal)) { return "identity-mismatch"; } if (!string.Equals(expected.Id, actual.Id, StringComparison.Ordinal)) { return "id-mismatch"; } if (!string.Equals(expected.FirstName, actual.FirstName, StringComparison.Ordinal)) { return "first-name-mismatch"; } if (!string.Equals(expected.LastName, actual.LastName, StringComparison.Ordinal)) { return "last-name-mismatch"; } if (expected.IsMale != actual.IsMale) { return "sex-mismatch"; } if (expected.AppearanceIndex != actual.AppearanceIndex) { return "appearance-mismatch"; } if (!string.Equals(expected.PropertyCode, actual.PropertyCode, StringComparison.Ordinal)) { return "property-mismatch"; } if (expected.PaidForToday != actual.PaidForToday) { return "paid-state-mismatch"; } if (expected.MoveItem != actual.MoveItem) { return "move-item-mismatch"; } if (!SameGuid(expected.HomeGuid, actual.HomeGuid)) { return "home-mismatch"; } if (!SameGuidSet(expected.StationGuids, actual.StationGuids)) { return "stations-mismatch"; } return null; } private static bool SameGuid(string expected, string actual) { if (string.IsNullOrWhiteSpace(expected) || string.IsNullOrWhiteSpace(actual)) { if (string.IsNullOrWhiteSpace(expected)) { return string.IsNullOrWhiteSpace(actual); } return false; } if (Guid.TryParse(expected, out var result) && Guid.TryParse(actual, out var result2)) { return result == result2; } return false; } private static bool SameGuidSet(IReadOnlyList expected, IReadOnlyList actual) { HashSet hashSet = expected.Select(CanonicalGuid).ToHashSet(StringComparer.Ordinal); HashSet hashSet2 = actual.Select(CanonicalGuid).ToHashSet(StringComparer.Ordinal); if (hashSet.Count == expected.Count && hashSet2.Count == actual.Count) { return hashSet.SetEquals(hashSet2); } return false; } private static string CanonicalGuid(string value) { if (!Guid.TryParse(value, out var result)) { return value; } return result.ToString("D"); } private void PreserveUnsupportedRecords() { foreach (Entry item in _entries.Where((Entry entry) => !entry.Descriptor.IsSupportedPackager)) { Preserve(item, "unsupported employee type"); } } private void Preserve(Entry entry, string reason) { if ((!entry.Descriptor.IsSupportedPackager || reason.Contains("malformed", StringComparison.OrdinalIgnoreCase)) && !entry.IsUnsupported) { entry.IsUnsupported = true; _unsupportedByGuid.TryAdd(entry.Descriptor.Guid, new FishWarehouseUnsupportedEmployeeRecord(entry.Descriptor.Guid, entry.Descriptor.DataType, entry.Descriptor.Identity, entry.Descriptor.RawJson)); _log($"WARNING: Fish Warehouse employee replay preserved unsupported record (guid={entry.Descriptor.Guid}, type={entry.Descriptor.DataType}): {reason}."); } } private FishWarehouseEmployeeReplayResult Fail(string reason) { foreach (Entry item in _entries.Where((Entry entry) => !entry.IsReplayed && !entry.Descriptor.IsSupportedPackager)) { Preserve(item, "replay did not complete"); } return Result = BuildResult(FishWarehouseEmployeeReplayState.Failed, reason); } private FishWarehouseEmployeeReplayResult BuildResult(FishWarehouseEmployeeReplayState state, string? failureReason) { int num = _entries.Count((Entry entry) => entry.Descriptor.IsSupportedPackager); int num2 = _entries.Count((Entry entry) => entry.IsReplayed); string primaryFailureReason = _entries.Select((Entry entry) => entry.PrimaryFailureReason).FirstOrDefault((string reason) => reason != null); return new FishWarehouseEmployeeReplayResult(state, _entries.Count, num, num2, _unsupportedByGuid.Values.ToArray(), state == FishWarehouseEmployeeReplayState.Succeeded && num2 == num, primaryFailureReason, failureReason, _adapter.InventoryRestoredEmployeeGuids.ToArray()); } } public sealed class FishWarehouseFrontageClearanceHost { private sealed record Candidate(GameObject Root, FishWarehouseFrontageCandidate Snapshot); private sealed record ClearedObject(GameObject Root, bool OriginalActiveSelf); private const string RegionPath = "Map/Hyland Point/Region_Docks"; private readonly List _clearedObjects = new List(); private bool _ready; public bool IsReady => _ready; public bool TryAttach(Property property, Action? log = null) { if (_ready) { return true; } if (property == null) { return false; } GameObject[] array = (from root in (from gameObject in ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where(delegate(GameObject gameObject) { //IL_0004: 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) if (gameObject != null) { Scene scene = gameObject.scene; return ((Scene)(ref scene)).IsValid(); } return false; }).ToArray() where ((Object)gameObject).name.Contains("Container", StringComparison.OrdinalIgnoreCase) select FindContainerRoot(gameObject.transform) into root where root != null select root).DistinctBy((GameObject root) => ((Object)root).GetInstanceID()) where GetTransformPath(root.transform).StartsWith("Map/Hyland Point/Region_Docks", StringComparison.Ordinal) select (root)).ToArray(); Candidate[] array2 = (from root in array where !GetTransformPath(root.transform).Contains("Fish Warehouse", StringComparison.OrdinalIgnoreCase) select CreateCandidate(root, ((Component)property).transform)).ToArray(); Candidate[] array3 = array2.Where((Candidate candidate2) => FishWarehouseFrontageClearanceDefinition.Evaluate(candidate2.Snapshot) == FishWarehouseFrontageClearanceDecision.Clear).ToArray(); if (array3.Length == 0) { log?.Invoke($"Fish Warehouse frontage clearance found no safe static container roots in the dock envelope (containerRoots={array.Length}, candidates={array2.Length}); no scene objects were changed."); return false; } try { Candidate[] array4 = array3; foreach (Candidate candidate in array4) { _clearedObjects.Add(new ClearedObject(candidate.Root, candidate.Root.activeSelf)); candidate.Root.SetActive(false); } _ready = true; return true; } catch (Exception ex) { Restore(log); log?.Invoke("Fish Warehouse frontage clearance failed safely: " + ex.Message); return false; } } public bool Dispose(Action? log = null) { return Restore(log); } private bool Restore(Action? log) { int num = 0; List list = new List(); foreach (ClearedObject clearedObject in _clearedObjects) { try { if ((Object)(object)clearedObject.Root == (Object)null) { list.Add(clearedObject); log?.Invoke($"Fish Warehouse frontage clearance could not restore destroyed scene object (originally active={clearedObject.OriginalActiveSelf})."); } else { clearedObject.Root.SetActive(clearedObject.OriginalActiveSelf); num++; } } catch (Exception ex) { list.Add(clearedObject); log?.Invoke("Fish Warehouse frontage clearance could not restore scene object: " + ex.Message); } } _clearedObjects.Clear(); _clearedObjects.AddRange(list); _ready = false; if (num > 0) { log?.Invoke($"Fish Warehouse frontage clearance restored {num} scene object(s)."); } if (list.Count > 0) { log?.Invoke($"Fish Warehouse frontage clearance retained {list.Count} scene object(s) for diagnostics after partial restore; cleanup remains partial."); } return list.Count == 0; } private static Candidate CreateCandidate(GameObject root, Transform propertyRoot) { //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_001d: 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_0110: 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_0128: Unknown result type (might be due to invalid IL or missing references) Il2CppArrayBase componentsInChildren = root.GetComponentsInChildren(true); Il2CppArrayBase componentsInChildren2 = root.GetComponentsInChildren(true); Bounds val = ToLocalBounds(GetCombinedBounds((IReadOnlyList)componentsInChildren, (IReadOnlyList)componentsInChildren2), propertyRoot); string[] componentTypeNames = (from component in ((IEnumerable)root.GetComponentsInChildren(true)).Select((Transform component) => (Component)(object)component).Concat(((IEnumerable)componentsInChildren).Select((Renderer component) => (Component)(object)component)).Concat(((IEnumerable)componentsInChildren2).Select((Collider component) => (Component)(object)component)) select ((object)component).GetType().FullName ?? ((object)component).GetType().Name).ToArray(); bool hasUnknownBehaviour = root.GetComponentsInChildren(true).Length > 0; FishWarehouseFrontageCandidate snapshot = new FishWarehouseFrontageCandidate(GetTransformPath(root.transform), root.activeSelf, root.isStatic, componentsInChildren.Length > 0, componentsInChildren2.Length > 0, (IReadOnlyCollection)(object)componentTypeNames, new FishWarehouseFrontageBounds(((Bounds)(ref val)).min.x, ((Bounds)(ref val)).max.x, ((Bounds)(ref val)).min.z, ((Bounds)(ref val)).max.z), HasProtectedAncestor(root.transform), hasUnknownBehaviour); return new Candidate(root, snapshot); } private static bool HasProtectedAncestor(Transform candidate) { for (Transform parent = candidate.parent; parent != null; parent = parent.parent) { if (FishWarehouseFrontageClearanceDefinition.IsProtectedPath(GetTransformPath(parent)) || ((IEnumerable)((Component)parent).GetComponents()).Any((MonoBehaviour component) => FishWarehouseFrontageClearanceDefinition.IsProtectedTypeName(((object)component).GetType().FullName ?? ((object)component).GetType().Name))) { return true; } } return false; } private static GameObject? FindContainerRoot(Transform start) { GameObject result = null; for (Transform val = start; val != null; val = val.parent) { if (((Object)val).name.Contains("Container", StringComparison.OrdinalIgnoreCase)) { result = ((Component)val).gameObject; } } return result; } private static Bounds GetCombinedBounds(IReadOnlyList renderers, IReadOnlyList colliders) { //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) //IL_0034: 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_0078: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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) //IL_0099: Unknown result type (might be due to invalid IL or missing references) Bounds bounds = default(Bounds); ((Bounds)(ref bounds))..ctor(Vector3.zero, Vector3.zero); bool flag = false; foreach (Renderer renderer in renderers) { if (!flag) { bounds = renderer.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(renderer.bounds); } } foreach (Collider collider in colliders) { if (!flag) { bounds = collider.bounds; flag = true; } else { ((Bounds)(ref bounds)).Encapsulate(collider.bounds); } } return bounds; } private static Bounds ToLocalBounds(Bounds worldBounds, Transform reference) { //IL_000a: 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_0022: 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_0031: 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_0046: 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_005c: Unknown result type (might be due to invalid IL or missing references) //IL_0061: Unknown result type (might be due to invalid IL or missing references) //IL_006a: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_0082: 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_0091: 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_00a6: 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_00bc: 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_00ca: 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_00e2: 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) //IL_00fa: 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_0112: 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) //IL_012a: 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_0142: 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) //IL_015a: 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_0172: Unknown result type (might be due to invalid IL or missing references) //IL_017c: 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_01a1: Unknown result type (might be due to invalid IL or missing references) //IL_01a6: 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_01c5: Unknown result type (might be due to invalid IL or missing references) //IL_01c8: Unknown result type (might be due to invalid IL or missing references) //IL_01e2: Unknown result type (might be due to invalid IL or missing references) Vector3[] array = ((IEnumerable)(object)new Vector3[8] { new Vector3(((Bounds)(ref worldBounds)).min.x, ((Bounds)(ref worldBounds)).min.y, ((Bounds)(ref worldBounds)).min.z), new Vector3(((Bounds)(ref worldBounds)).min.x, ((Bounds)(ref worldBounds)).min.y, ((Bounds)(ref worldBounds)).max.z), new Vector3(((Bounds)(ref worldBounds)).min.x, ((Bounds)(ref worldBounds)).max.y, ((Bounds)(ref worldBounds)).min.z), new Vector3(((Bounds)(ref worldBounds)).min.x, ((Bounds)(ref worldBounds)).max.y, ((Bounds)(ref worldBounds)).max.z), new Vector3(((Bounds)(ref worldBounds)).max.x, ((Bounds)(ref worldBounds)).min.y, ((Bounds)(ref worldBounds)).min.z), new Vector3(((Bounds)(ref worldBounds)).max.x, ((Bounds)(ref worldBounds)).min.y, ((Bounds)(ref worldBounds)).max.z), new Vector3(((Bounds)(ref worldBounds)).max.x, ((Bounds)(ref worldBounds)).max.y, ((Bounds)(ref worldBounds)).min.z), new Vector3(((Bounds)(ref worldBounds)).max.x, ((Bounds)(ref worldBounds)).max.y, ((Bounds)(ref worldBounds)).max.z) }).Select((Func)reference.InverseTransformPoint).ToArray(); Bounds result = default(Bounds); ((Bounds)(ref result))..ctor(array[0], Vector3.zero); foreach (Vector3 item in array.Skip(1)) { ((Bounds)(ref result)).Encapsulate(item); } return result; } private static string GetTransformPath(Transform transform) { Stack stack = new Stack(); for (Transform val = transform; val != null; val = val.parent) { stack.Push(((Object)val).name); } return string.Join("/", stack); } } public sealed class FishWarehouseGarageCrossingHost { private readonly struct SuspendedAgent { public NavMeshAgent Agent { get; } public bool UpdatePosition { get; } public bool UpdateRotation { get; } public bool IsStopped { get; } public Vector3 Destination { get; } public SuspendedAgent(NavMeshAgent agent, bool updatePosition, bool updateRotation, bool isStopped, Vector3 destination) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0020: Unknown result type (might be due to invalid IL or missing references) Agent = agent; UpdatePosition = updatePosition; UpdateRotation = updateRotation; IsStopped = isStopped; Destination = destination; } } private const float GarageLinkScopeRadius = 8f; private const float GlideOvershoot = 0.9f; private const int GlideCooldownFrames = 90; private readonly Dictionary _states = new Dictionary(); private readonly Dictionary _suspended = new Dictionary(); private readonly Dictionary _glideTargets = new Dictionary(); private readonly Dictionary _cooldownUntilFrame = new Dictionary(); private bool _active; private Vector3 _garageWorldCenter; public bool IsActive => _active; public void Activate(Vector3 garageWorldCenter) { //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) _garageWorldCenter = garageWorldCenter; _active = true; } public void Tick(Property property, float deltaTime, bool hostAuthority) { if (!_active || !hostAuthority) { return; } try { foreach (NavMeshAgent item in EnumerateAgents(property)) { TickAgent(item, deltaTime); } } catch { } } public void Deactivate() { SuspendedAgent[] array = _suspended.Values.ToArray(); for (int i = 0; i < array.Length; i++) { RestoreAgent(array[i]); } _suspended.Clear(); _states.Clear(); _glideTargets.Clear(); _cooldownUntilFrame.Clear(); _active = false; } private void TickAgent(NavMeshAgent agent, float deltaTime) { //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_00f4: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_008d: 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_0095: 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_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0101: 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_00a8: 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_0146: 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_00b0: 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_00b4: 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_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_00d6: 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_00db: 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_00df: 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_00eb: 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) int instanceID = ((Object)agent).GetInstanceID(); if (!_states.TryGetValue(instanceID, out FishWarehouseGarageCrossingAgentState value)) { value = new FishWarehouseGarageCrossingAgentState(); _states[instanceID] = value; } Vector3 start; Vector3 end; bool flag = TryGetGarageLink(agent, out start, out end); Vector3 position = ((Component)agent).transform.position; if (flag && value.Phase == FishWarehouseGarageCrossingPhase.Idle && _cooldownUntilFrame.TryGetValue(instanceID, out var value2) && Time.frameCount < value2) { flag = false; } Vector3 value4; if (value.Phase == FishWarehouseGarageCrossingPhase.Gliding && _glideTargets.TryGetValue(instanceID, out var value3)) { value4 = value3; } else if (flag) { bool num = Vector3.Distance(position, start) >= Vector3.Distance(position, end); Vector3 val = (num ? start : end); Vector3 val2 = (num ? end : start); Vector3 val3 = val - val2; val3 = ((((Vector3)(ref val3)).sqrMagnitude > 1E-06f) ? ((Vector3)(ref val3)).normalized : Vector3.zero); value4 = val + val3 * 0.9f; } else { value4 = position; } FishWarehouseGarageCrossingDecision fishWarehouseGarageCrossingDecision = FishWarehouseGarageCrossingDefinition.Decide(sample: new FishWarehouseGarageCrossingSample(flag, SafeSpeed(agent), ToModel(position), ToModel(value4)), state: value, deltaTime: deltaTime, hostAuthority: true); switch (fishWarehouseGarageCrossingDecision.Kind) { case FishWarehouseGarageCrossingActionKind.BeginGlide: _glideTargets[instanceID] = value4; BeginGlide(agent, instanceID); break; case FishWarehouseGarageCrossingActionKind.StepGlide: StepGlide(agent, fishWarehouseGarageCrossingDecision.GlideTarget); break; case FishWarehouseGarageCrossingActionKind.CompleteGlide: CompleteGlide(agent, instanceID, fishWarehouseGarageCrossingDecision.GlideTarget); break; } } private void BeginGlide(NavMeshAgent agent, int id) { //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_001f: 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_005b: Unknown result type (might be due to invalid IL or missing references) try { if (!_suspended.ContainsKey(id)) { Vector3 destination = SafeDestination(agent, ((Component)agent).transform.position); _suspended[id] = new SuspendedAgent(agent, agent.updatePosition, agent.updateRotation, agent.isStopped, destination); agent.updatePosition = false; agent.updateRotation = false; agent.isStopped = true; agent.velocity = Vector3.zero; } } catch { RestoreById(id); } } private void StepGlide(NavMeshAgent agent, FishWarehouseNativeNavigationVector target) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) try { ((Component)agent).transform.position = ToUnity(target); } catch { } } private void CompleteGlide(NavMeshAgent agent, int id, FishWarehouseNativeNavigationVector exit) { //IL_001f: 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_0024: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) SuspendedAgent value; Vector3 destination = (_suspended.TryGetValue(id, out value) ? value.Destination : ((Component)agent).transform.position); try { try { agent.CompleteOffMeshLink(); } catch { } RestoreById(id); try { agent.SetDestination(destination); } catch { } _glideTargets.Remove(id); _cooldownUntilFrame[id] = Time.frameCount + 90; } catch { RestoreById(id); } } private bool TryGetGarageLink(NavMeshAgent agent, out Vector3 start, out Vector3 end) { //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_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) //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_0046: 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_0051: 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_005c: 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_006c: Unknown result type (might be due to invalid IL or missing references) start = Vector3.zero; end = Vector3.zero; try { if (!agent.isOnOffMeshLink) { return false; } OffMeshLinkData currentOffMeshLinkData = agent.currentOffMeshLinkData; if (!((OffMeshLinkData)(ref currentOffMeshLinkData)).valid) { return false; } start = ((OffMeshLinkData)(ref currentOffMeshLinkData)).startPos; end = ((OffMeshLinkData)(ref currentOffMeshLinkData)).endPos; return Vector3.Distance((start + end) * 0.5f, _garageWorldCenter) <= 8f; } catch { return false; } } private void RestoreById(int id) { if (_suspended.TryGetValue(id, out var value)) { RestoreAgent(value); _suspended.Remove(id); } _glideTargets.Remove(id); } private static void RestoreAgent(SuspendedAgent suspended) { try { NavMeshAgent agent = suspended.Agent; if (agent != null && !((Object)(object)agent == (Object)null)) { agent.updatePosition = suspended.UpdatePosition; agent.updateRotation = suspended.UpdateRotation; agent.isStopped = suspended.IsStopped; } } catch { } } private static IEnumerable EnumerateAgents(Property property) { if (property == null || (Object)(object)property == (Object)null || property.Employees == null) { yield break; } Enumerator enumerator = property.Employees.GetEnumerator(); while (enumerator.MoveNext()) { Employee current = enumerator.Current; NavMeshAgent val = null; try { if (current != null && (Object)(object)current != (Object)null && ((NPC)current).Movement != null && (Object)(object)((NPC)current).Movement != (Object)null && ((NPC)current).Movement.Agent != null && (Object)(object)((NPC)current).Movement.Agent != (Object)null) { val = ((NPC)current).Movement.Agent; } } catch { val = null; } if (val != null) { yield return val; } } } private static float SafeSpeed(NavMeshAgent agent) { //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) try { Vector3 velocity = agent.velocity; return ((Vector3)(ref velocity)).magnitude; } catch { return 0f; } } private static FishWarehouseNativeNavigationVector ToModel(Vector3 value) { //IL_0000: 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) return new FishWarehouseNativeNavigationVector(value.x, value.y, value.z); } private static Vector3 ToUnity(FishWarehouseNativeNavigationVector value) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(value.X, value.Y, value.Z); } private static Vector3 SafeDestination(NavMeshAgent agent, Vector3 fallback) { //IL_001d: 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_0014: 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_0019: 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) try { return (agent.hasPath || agent.pathPending) ? agent.destination : fallback; } catch { return fallback; } } } public sealed record FishWarehouseGarageOpeningPreflightResult(Transform Anchor, FishWarehouseGarageOpeningProjection Projection, IReadOnlyList TargetFaces, IReadOnlyList TargetColliders, FishWarehouseTransitionPrism TransitionPrism); public sealed class FishWarehouseGarageOpeningHost { private sealed class UnityOverlapBoxQuery : IFishWarehouseGarageOverlapBoxQuery { private readonly FishWarehouseGarageRuntimeAnchor _anchor; public UnityOverlapBoxQuery(FishWarehouseGarageRuntimeAnchor anchor) { _anchor = anchor; } public IReadOnlyList OverlapBox(FishWarehouseGarageOverlapBoxRequest request) { //IL_002d: 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_00a0: Unknown result type (might be due to invalid IL or missing references) return Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)Physics.OverlapBox(new Vector3(request.WorldCenter.X, request.WorldCenter.Y, request.WorldCenter.Z), new Vector3(request.HalfExtents.X, request.HalfExtents.Y, request.HalfExtents.Z), new Quaternion(request.Rotation.X, request.Rotation.Y, request.Rotation.Z, request.Rotation.W), -1, (QueryTriggerInteraction)1)).Where(IsAlive).DistinctBy((Collider collider) => ((Object)collider).GetInstanceID()) .Select(delegate(Collider collider) { //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_0017: 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_002f: 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_004c: 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) Bounds bounds = collider.bounds; return new FishWarehouseGarageOverlapBoxHit(((Object)collider).GetInstanceID().ToString(), FishWarehouseGarageRuntimeEvidenceMapper.ToAnchorLocalBounds(new FishWarehouseWorldBounds(new FishWarehouseGarageVector(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).center.y, ((Bounds)(ref bounds)).center.z), new FishWarehouseGarageVector(((Bounds)(ref bounds)).extents.x, ((Bounds)(ref bounds)).extents.y, ((Bounds)(ref bounds)).extents.z)), _anchor)); }) .ToArray(); } } private sealed class UnityTargetState : IFishWarehouseGarageTargetState { private readonly IReadOnlyDictionary _renderers; private readonly IReadOnlyDictionary _colliders; public UnityTargetState(IEnumerable renderers, IEnumerable colliders) { _renderers = renderers.ToDictionary((Renderer renderer) => ((Object)renderer).GetInstanceID().ToString(), StringComparer.Ordinal); _colliders = colliders.ToDictionary((Collider collider) => ((Object)collider).GetInstanceID().ToString(), StringComparer.Ordinal); } public bool TryGetEnabled(FishWarehouseGarageTargetComponent component, out bool enabled) { enabled = false; try { switch (component.Kind) { case FishWarehouseGarageTargetComponentKind.Renderer: { if (_renderers.TryGetValue(component.Identity, out Renderer value2) && IsAlive((Object?)(object)value2)) { enabled = value2.enabled; return true; } break; } case FishWarehouseGarageTargetComponentKind.Collider: { if (_colliders.TryGetValue(component.Identity, out Collider value) && IsAlive((Object?)(object)value)) { enabled = value.enabled; return true; } break; } } return false; } catch { return false; } } public bool TrySetEnabled(FishWarehouseGarageTargetComponent component, bool enabled) { try { switch (component.Kind) { case FishWarehouseGarageTargetComponentKind.Renderer: { if (_renderers.TryGetValue(component.Identity, out Renderer value2) && IsAlive((Object?)(object)value2)) { value2.enabled = enabled; return true; } break; } case FishWarehouseGarageTargetComponentKind.Collider: { if (_colliders.TryGetValue(component.Identity, out Collider value) && IsAlive((Object?)(object)value)) { value.enabled = enabled; return true; } break; } } return false; } catch { return false; } } } private const float FloorSupportTolerance = 0.2f; private const float FloorRayStartAboveCeiling = 1f; private FishWarehouseGarageOpeningPreflightResult? _preflight; private FishWarehouseGarageTargetMutationState? _mutation; private IFishWarehouseGarageOverlapBoxQuery? _physicsQuery; private bool _opened; public FishWarehouseGarageOpeningPreflightResult? Preflight => _preflight; public FishWarehouseGarageOpeningHost(IFishWarehouseGarageOverlapBoxQuery? physicsQuery = null) { _physicsQuery = physicsQuery; } public bool TryPreflight(Transform anchor, Transform propertyRoot, out FishWarehouseGarageOpeningPreflightResult? result, Action? log = null) { result = null; if ((object)_preflight != null) { result = _preflight; return true; } if (!IsAlive((Object?)(object)anchor) || !IsAlive((Object?)(object)propertyRoot)) { log?.Invoke("Fish Warehouse garage preflight rejected: anchor or property root was unavailable."); return false; } try { if (!FishWarehouseTransformSignatureDefinition.IsCoincident(ToTransformSignature(anchor), ToTransformSignature(propertyRoot))) { log?.Invoke("Fish Warehouse garage preflight rejected: anchor/property-root transform signatures were not coincident."); return false; } FishWarehouseGarageRuntimeAnchor anchor2 = ToRuntimeAnchor(anchor); if (!TryResolveApprovedScene(out Transform primaryRoot, out Transform primaryPanel, out Transform alternateRoot, out FishWarehouseGarageSceneSelection selection, log) || primaryRoot == null || primaryPanel == null || alternateRoot == null || (object)selection == null) { return false; } if (!TryCreateExactTargetSet(selection, primaryRoot, primaryPanel, alternateRoot, out FishWarehouseGarageTargetSet targetSet, out Renderer[] targetFaces, out Collider[] targetColliders, out Renderer primaryPanelRenderer, out Renderer alternateRenderer, log) || (object)targetSet == null || primaryPanelRenderer == null || alternateRenderer == null) { return false; } if (!TryCreateRuntimeEvidence(primaryRoot, primaryPanel, primaryPanelRenderer, out var evidence) || !TryCreateRuntimeEvidence(alternateRoot, alternateRoot, alternateRenderer, out var evidence2) || !FishWarehouseGarageRuntimeEvidenceMapper.TryMap(anchor2, evidence, out var mapping) || !FishWarehouseGarageRuntimeEvidenceMapper.TryMap(anchor2, evidence2, out var mapping2)) { log?.Invoke("Fish Warehouse garage preflight rejected: selected renderer runtime evidence could not produce a safety envelope and physical plane."); return false; } FishWarehouseAnchorLocalBounds safetyEnvelope = mapping.SafetyEnvelope; FishWarehousePhysicalPanelPlane physicalPanelPlane = mapping.PhysicalPanelPlane; FishWarehouseAnchorLocalBounds safetyEnvelope2 = mapping2.SafetyEnvelope; FishWarehousePhysicalPanelPlane physicalPanelPlane2 = mapping2.PhysicalPanelPlane; if (!FishWarehouseGarageOpeningGeometryDefinition.IsNorthWallPhysicalPlane(safetyEnvelope, physicalPanelPlane) || !FishWarehouseGarageOpeningGeometryDefinition.IsNorthWallPhysicalPlane(safetyEnvelope2, physicalPanelPlane2) || !FishWarehouseGarageOpeningGeometryDefinition.AreCoincidentPhysicalFaces(safetyEnvelope, physicalPanelPlane, safetyEnvelope2, physicalPanelPlane2)) { log?.Invoke($"Fish Warehouse garage preflight rejected: independently observed primary/alternate renderers did not agree with coincident physical north-wall faces (primaryEnvelope={FormatBounds(safetyEnvelope)}, alternateEnvelope={FormatBounds(safetyEnvelope2)})."); return false; } if (!FishWarehouseGarageOpeningDefinition.TryProject(CreateCandidates(primaryRoot, primaryPanel, alternateRoot, safetyEnvelope, physicalPanelPlane, safetyEnvelope2, physicalPanelPlane2, Array.Empty(), hasFloorSupport: true), out FishWarehouseGarageOpeningProjection projection) || (object)projection == null) { log?.Invoke("Fish Warehouse garage preflight rejected: live panel extrema did not satisfy the approved opening projection contract."); return false; } HashSet hashSet = targetColliders.Select((Collider collider) => ((Object)collider).GetInstanceID()).ToHashSet(); IFishWarehouseGarageOverlapBoxQuery fishWarehouseGarageOverlapBoxQuery = _physicsQuery ?? new UnityOverlapBoxQuery(anchor2); if (!FishWarehouseGarageOverlapBoxClearanceDefinition.TryQueryNonTargetBounds(projection.TransitionPrism, anchor2, fishWarehouseGarageOverlapBoxQuery, hashSet.Select((int id) => id.ToString()), out IReadOnlyList nonTargetBounds)) { log?.Invoke("Fish Warehouse garage preflight rejected: the elevated transition-prism overlap query could not produce valid non-target bounds."); return false; } bool num = FishWarehouseGarageOpeningClearanceDefinition.IsTraversableVolumeClear(projection.TransitionPrism, nonTargetBounds); bool flag = FishWarehouseGarageOpeningClearanceDefinition.IsFloorBandClear(projection.TransitionPrism, nonTargetBounds); bool flag2 = HasContinuousFloorSupport(anchor, projection.TransitionPrism, hashSet, log); FishWarehouseGarageOpeningCandidate[] candidates = CreateCandidates(primaryRoot, primaryPanel, alternateRoot, safetyEnvelope, physicalPanelPlane, safetyEnvelope2, physicalPanelPlane2, nonTargetBounds, flag2); if (!num || !flag || !flag2 || !FishWarehouseGarageOpeningDefinition.TryProject(candidates, out FishWarehouseGarageOpeningProjection projection2) || (object)projection2 == null) { log?.Invoke("Fish Warehouse garage preflight rejected: transition-prism clearance, floor support, or final projection validation failed before mutation."); return false; } result = new FishWarehouseGarageOpeningPreflightResult(anchor, projection2, Array.AsReadOnly(targetFaces), Array.AsReadOnly(targetColliders), projection2.TransitionPrism); _preflight = result; _mutation = new FishWarehouseGarageTargetMutationState(targetSet); _physicsQuery = fishWarehouseGarageOverlapBoxQuery; return true; } catch (Exception ex) { log?.Invoke("Fish Warehouse garage preflight failed safely before mutation: " + ex.Message); return false; } } public bool TryOpen(Action? log = null) { if (_opened) { return true; } if ((object)_preflight == null || _mutation == null) { log?.Invoke("Fish Warehouse garage opening rejected: no successful preflight was available."); return false; } try { UnityTargetState state = new UnityTargetState(_preflight.TargetFaces, _preflight.TargetColliders); if (!_mutation.TryOpen(preflightPassed: true, state, delegate { HashSet source = (from collider in _preflight.TargetColliders.Where(IsAlive) select ((Object)collider).GetInstanceID()).ToHashSet(); IReadOnlyList nonTargetBounds; return _physicsQuery != null && FishWarehouseGarageOverlapBoxClearanceDefinition.TryQueryNonTargetBounds(_preflight.TransitionPrism, ToRuntimeAnchor(_preflight.Anchor), _physicsQuery, source.Select((int id) => id.ToString()), out nonTargetBounds) && FishWarehouseGarageOpeningClearanceDefinition.IsTraversableVolumeClear(_preflight.TransitionPrism, nonTargetBounds); })) { log?.Invoke("Fish Warehouse garage opening rejected after mutation: the target-only state change or post-open transition-prism validation failed; recorded target states were restored."); return false; } _opened = true; return true; } catch (Exception ex) { log?.Invoke("Fish Warehouse garage opening failed safely after mutation: " + ex.Message + ". Restoring recorded states."); Restore(log); return false; } } public bool Restore(Action? log = null) { bool num = (object)_preflight == null || _mutation == null || _mutation.Restore(new UnityTargetState(_preflight.TargetFaces, _preflight.TargetColliders)); _opened = false; if (!num) { log?.Invoke("Fish Warehouse garage restore was partial because a recorded direct target component no longer existed or rejected restoration."); } return num; } public bool Dispose(Action? log = null) { bool result = Restore(log); _preflight = null; _mutation = null; return result; } private static FishWarehouseGarageOpeningCandidate[] CreateCandidates(Transform primaryRoot, Transform primaryPanel, Transform alternateRoot, FishWarehouseAnchorLocalBounds panelBounds, FishWarehousePhysicalPanelPlane physicalPanelPlane, FishWarehouseAnchorLocalBounds alternateBounds, FishWarehousePhysicalPanelPlane alternatePhysicalPanelPlane, IReadOnlyList survivingNonTargetColliders, bool hasFloorSupport) { return new FishWarehouseGarageOpeningCandidate[2] { new FishWarehouseGarageOpeningCandidate(GetHierarchyPath(primaryRoot), ((Object)primaryPanel).name, panelBounds, physicalPanelPlane, survivingNonTargetColliders, hasFloorSupport), new FishWarehouseGarageOpeningCandidate(GetHierarchyPath(alternateRoot), ((Object)alternateRoot).name, alternateBounds, alternatePhysicalPanelPlane, survivingNonTargetColliders, hasFloorSupport) }; } private static bool TryResolveApprovedScene(out Transform? primaryRoot, out Transform? primaryPanel, out Transform? alternateRoot, out FishWarehouseGarageSceneSelection? selection, Action? log) { primaryRoot = null; primaryPanel = null; alternateRoot = null; selection = null; GameObject[] source = ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where(delegate(GameObject gameObject) { //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) if (IsAlive((Object?)(object)gameObject)) { Scene scene = gameObject.scene; return ((Scene)(ref scene)).IsValid(); } return false; }).DistinctBy((GameObject gameObject) => ((Object)gameObject).GetInstanceID()).ToArray(); if (!FishWarehouseGarageSceneSelectionDefinition.TrySelect(source.Select((GameObject gameObject) => new FishWarehouseGarageSceneNode(GetHierarchyPath(gameObject.transform), ((Object)gameObject).name, (gameObject.transform.parent == null) ? null : GetHierarchyPath(gameObject.transform.parent))), out selection) || (object)selection == null) { log?.Invoke("Fish Warehouse garage preflight rejected: the approved primary root, exact direct child, and alternate root did not resolve uniquely."); return false; } FishWarehouseGarageSceneSelection resolvedSelection = selection; primaryRoot = source.Single((GameObject gameObject) => string.Equals(GetHierarchyPath(gameObject.transform), resolvedSelection.PrimaryRootPath, StringComparison.Ordinal)).transform; primaryPanel = source.Single((GameObject gameObject) => string.Equals(GetHierarchyPath(gameObject.transform), resolvedSelection.PrimaryPanelPath, StringComparison.Ordinal)).transform; alternateRoot = source.Single((GameObject gameObject) => string.Equals(GetHierarchyPath(gameObject.transform), resolvedSelection.AlternateRootPath, StringComparison.Ordinal)).transform; return true; } private static bool TryCreateExactTargetSet(FishWarehouseGarageSceneSelection selection, Transform primaryRoot, Transform primaryPanel, Transform alternateRoot, out FishWarehouseGarageTargetSet? targetSet, out Renderer[] targetFaces, out Collider[] targetColliders, out Renderer? primaryPanelRenderer, out Renderer? alternateRenderer, Action? log) { targetSet = null; targetFaces = Array.Empty(); targetColliders = Array.Empty(); primaryPanelRenderer = null; alternateRenderer = null; List components = new List(); Dictionary renderersByIdentity = new Dictionary(StringComparer.Ordinal); Dictionary collidersByIdentity = new Dictionary(StringComparer.Ordinal); AddDirectComponents(primaryRoot, selection.PrimaryRootPath, components, renderersByIdentity, collidersByIdentity); AddDirectComponents(primaryPanel, selection.PrimaryPanelPath, components, renderersByIdentity, collidersByIdentity); AddDirectComponents(alternateRoot, selection.AlternateRootPath, components, renderersByIdentity, collidersByIdentity); if (!FishWarehouseGarageTargetSetDefinition.TryCreate(selection, components, out targetSet) || (object)targetSet == null) { log?.Invoke("Fish Warehouse garage preflight rejected: the direct primary-root, selected-child, and alternate-root renderer/collider census did not match the approved target set."); return false; } targetFaces = targetSet.Renderers.Select((FishWarehouseGarageTargetComponent component) => renderersByIdentity[component.Identity]).ToArray(); targetColliders = targetSet.Colliders.Select((FishWarehouseGarageTargetComponent component) => collidersByIdentity[component.Identity]).ToArray(); string identity = targetSet.Renderers.Single((FishWarehouseGarageTargetComponent component) => string.Equals(component.NodePath, selection.PrimaryPanelPath, StringComparison.Ordinal)).Identity; primaryPanelRenderer = renderersByIdentity[identity]; string identity2 = targetSet.Renderers.Single((FishWarehouseGarageTargetComponent component) => string.Equals(component.NodePath, selection.AlternateRootPath, StringComparison.Ordinal)).Identity; alternateRenderer = renderersByIdentity[identity2]; return true; } private static void AddDirectComponents(Transform node, string nodePath, ICollection components, IDictionary renderersByIdentity, IDictionary collidersByIdentity) { foreach (Renderer item in ((IEnumerable)((Component)node).GetComponents()).Where(IsAlive)) { string text = ((Object)item).GetInstanceID().ToString(); components.Add(new FishWarehouseGarageTargetComponent(nodePath, text, FishWarehouseGarageTargetComponentKind.Renderer, item.enabled)); renderersByIdentity.Add(text, item); } foreach (Collider item2 in ((IEnumerable)((Component)node).GetComponents()).Where(IsAlive)) { string text2 = ((Object)item2).GetInstanceID().ToString(); components.Add(new FishWarehouseGarageTargetComponent(nodePath, text2, FishWarehouseGarageTargetComponentKind.Collider, item2.enabled)); collidersByIdentity.Add(text2, item2); } } private static bool TryCreateRuntimeEvidence(Transform panelRoot, Transform expectedRendererTransform, Renderer renderer, out FishWarehouseGaragePanelRuntimeEvidence evidence) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_004f: 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_0067: 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_0084: 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) //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_00ae: 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_00be: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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_00d6: 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_00eb: 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_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_0103: 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_0113: 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_0126: Unknown result type (might be due to invalid IL or missing references) //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0132: 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_0143: 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_0155: Unknown result type (might be due to invalid IL or missing references) //IL_0165: Unknown result type (might be due to invalid IL or missing references) evidence = default(FishWarehouseGaragePanelRuntimeEvidence); MeshFilter component = ((Component)renderer).GetComponent(); Mesh val = ((component != null) ? component.sharedMesh : null); if (val == null || (Object)(object)((Component)renderer).transform != (Object)(object)expectedRendererTransform) { return false; } Vector3 lossyScale = ((Component)renderer).transform.lossyScale; Bounds bounds = renderer.bounds; Vector3 position = ((Component)renderer).transform.position; FishWarehouseWorldBounds worldRendererBounds = new FishWarehouseWorldBounds(new FishWarehouseGarageVector(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).center.y, ((Bounds)(ref bounds)).center.z), new FishWarehouseGarageVector(((Bounds)(ref bounds)).extents.x, ((Bounds)(ref bounds)).extents.y, ((Bounds)(ref bounds)).extents.z)); Bounds bounds2 = val.bounds; float x = ((Bounds)(ref bounds2)).center.x; bounds2 = val.bounds; float y = ((Bounds)(ref bounds2)).center.y; bounds2 = val.bounds; FishWarehouseGarageVector meshLocalCenter = new FishWarehouseGarageVector(x, y, ((Bounds)(ref bounds2)).center.z); bounds2 = val.bounds; float x2 = ((Bounds)(ref bounds2)).extents.x; bounds2 = val.bounds; float y2 = ((Bounds)(ref bounds2)).extents.y; bounds2 = val.bounds; evidence = new FishWarehouseGaragePanelRuntimeEvidence(worldRendererBounds, meshLocalCenter, new FishWarehouseGarageVector(x2, y2, ((Bounds)(ref bounds2)).extents.z), new FishWarehouseGarageVector(lossyScale.x, lossyScale.y, lossyScale.z), new FishWarehouseGarageVector(position.x, position.y, position.z), ToGarageQuaternion(panelRoot.rotation), ToGarageQuaternion(((Component)renderer).transform.rotation), renderer.isPartOfStaticBatch); return true; } private static bool HasContinuousFloorSupport(Transform anchor, FishWarehouseTransitionPrism prism, ISet targetColliderIds, Action? log) { //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_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) float[] array = new float[3] { prism.MinimumX, (prism.MinimumX + prism.MaximumX) / 2f, prism.MaximumX }; float[] array2 = new float[3] { prism.MinimumZ, (prism.MinimumZ + prism.MaximumZ) / 2f, prism.MaximumZ }; float num = 6.8f; float num2 = num - 0f + 0.2f; float[] array3 = array; foreach (float num3 in array3) { float[] array4 = array2; foreach (float num4 in array4) { if (!FishWarehouseGarageOpeningClearanceDefinition.IsFloorSupportedAt((from hit in Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)Physics.RaycastAll(anchor.TransformPoint(new Vector3(num3, num, num4)), -anchor.up, num2, -1, (QueryTriggerInteraction)1)) where IsAlive((Object?)(object)((RaycastHit)(ref hit)).collider) where !targetColliderIds.Contains(((Object)((RaycastHit)(ref hit)).collider).GetInstanceID()) select anchor.InverseTransformPoint(((RaycastHit)(ref hit)).point).y).ToArray(), 0.2f)) { log?.Invoke($"Fish Warehouse garage floor support missing at anchorLocal=({num3:0.000},{num4:0.000}); missing floor is fail-closed data."); return false; } } } return true; } private static FishWarehouseAnchorLocalBounds ToAnchorLocalBounds(IEnumerable worldBounds, Transform anchor) { //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_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_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_0040: 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_004e: 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_005a: Unknown result type (might be due to invalid IL or missing references) //IL_0061: 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) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007b: 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_0089: 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) //IL_0097: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_00aa: 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_00cb: 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) //IL_00de: 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_00ec: 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_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_0106: Unknown result type (might be due to invalid IL or missing references) //IL_010d: 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_011b: 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_0127: 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_0135: Unknown result type (might be due to invalid IL or missing references) //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: 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) //IL_0156: Unknown result type (might be due to invalid IL or missing references) //IL_0159: 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_0167: Unknown result type (might be due to invalid IL or missing references) List list = new List(); foreach (Bounds worldBound in worldBounds) { Bounds current = worldBound; Vector3 min = ((Bounds)(ref current)).min; Vector3 max = ((Bounds)(ref current)).max; Vector3[] array = (Vector3[])(object)new Vector3[8] { new Vector3(min.x, min.y, min.z), new Vector3(min.x, min.y, max.z), new Vector3(min.x, max.y, min.z), new Vector3(min.x, max.y, max.z), new Vector3(max.x, min.y, min.z), new Vector3(max.x, min.y, max.z), new Vector3(max.x, max.y, min.z), new Vector3(max.x, max.y, max.z) }; foreach (Vector3 val in array) { list.Add(new FishWarehouseGarageVector(val.x, val.y, val.z)); } } return FishWarehouseGarageOpeningGeometryDefinition.ToAnchorLocalBounds(list, delegate(FishWarehouseGarageVector worldCorner) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //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_0026: 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) Vector3 val2 = anchor.InverseTransformPoint(new Vector3(worldCorner.X, worldCorner.Y, worldCorner.Z)); return new FishWarehouseGarageVector(val2.x, val2.y, val2.z); }); } private static FishWarehouseAnchorLocalBounds ToAnchorLocalBounds(Bounds worldBounds, Transform anchor) { //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) return ToAnchorLocalBounds((IEnumerable)(object)new Bounds[1] { worldBounds }, anchor); } private static FishWarehouseTransformSignature ToTransformSignature(Transform transform) { //IL_0001: 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_0017: 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_0032: 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_004d: 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_0063: Unknown result type (might be due to invalid IL or missing references) return new FishWarehouseTransformSignature(new FishWarehouseTransformVector(transform.position.x, transform.position.y, transform.position.z), new FishWarehouseTransformVector(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z), new FishWarehouseTransformVector(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z)); } private static FishWarehouseGarageRuntimeAnchor ToRuntimeAnchor(Transform anchor) { //IL_0001: 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_0017: 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) return new FishWarehouseGarageRuntimeAnchor(new FishWarehouseGarageVector(anchor.position.x, anchor.position.y, anchor.position.z), ToGarageQuaternion(anchor.rotation)); } private static FishWarehouseGarageQuaternion ToGarageQuaternion(Quaternion rotation) { //IL_0000: 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_0012: Unknown result type (might be due to invalid IL or missing references) return new FishWarehouseGarageQuaternion(rotation.x, rotation.y, rotation.z, rotation.w); } private static string GetHierarchyPath(Transform transform) { Stack stack = new Stack(); for (Transform val = transform; val != null; val = val.parent) { stack.Push(((Object)val).name); } return string.Join("/", stack); } private static bool IsAlive(Object? value) { try { return value != null && value != (Object)null; } catch { return false; } } private static string FormatBounds(Bounds bounds) { //IL_001a: 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_0062: 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_00aa: 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) return $"center=({((Bounds)(ref bounds)).center.x:0.000},{((Bounds)(ref bounds)).center.y:0.000},{((Bounds)(ref bounds)).center.z:0.000});extents=({((Bounds)(ref bounds)).extents.x:0.000},{((Bounds)(ref bounds)).extents.y:0.000},{((Bounds)(ref bounds)).extents.z:0.000})"; } private static string FormatBounds(FishWarehouseAnchorLocalBounds bounds) { return $"min=({bounds.MinimumX:0.000},{bounds.MinimumY:0.000},{bounds.MinimumZ:0.000});max=({bounds.MaximumX:0.000},{bounds.MaximumY:0.000},{bounds.MaximumZ:0.000})"; } private static string FormatPrism(FishWarehouseTransitionPrism prism) { return $"min=({prism.MinimumX:0.000},{prism.MinimumY:0.000},{prism.MinimumZ:0.000});max=({prism.MaximumX:0.000},{prism.MaximumY:0.000},{prism.MaximumZ:0.000})"; } } public sealed class FishWarehouseInteriorRoomHost { private sealed class MeshData { public List Vertices { get; } = new List(); public List Triangles { get; } = new List(); } private GameObject? _root; private Mesh? _mesh; private IReadOnlyList _openings = Array.Empty(); public bool IsReady { get { if (IsAlive((Object?)(object)_root)) { return IsAlive((Object?)(object)_mesh); } return false; } } public GameObject? Root { get { if (!IsReady) { return null; } return _root; } } public IReadOnlyList Openings => _openings; public bool TryPrepare(Transform anchor, Transform propertyRoot, FishWarehouseGarageOpeningProjection garageOpening, Action? log = null) { //IL_004e: 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_0059: 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_009d: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Expected O, but got Unknown //IL_00c4: 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_00ee: 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) //IL_011f: Unknown result type (might be due to invalid IL or missing references) //IL_012f: Expected O, but got Unknown if (IsReady) { return true; } if (anchor == null || propertyRoot == null) { return false; } try { if (!FishWarehouseTransformSignatureDefinition.IsCoincident(ToTransformSignature(anchor), ToTransformSignature(propertyRoot))) { throw new InvalidOperationException("The anchor/property-root transform signatures were not coincident."); } Transform sourceRoot = FindSourceRoot(anchor); Transform val = FindEntranceDoor(sourceRoot); if (val == null) { throw new InvalidOperationException("The native Industrial Metal Door (Static) transform was not found."); } Vector3 val2 = anchor.InverseTransformPoint(val.position); if (!FishWarehouseInteriorRoomDefinition.TryPlanRoomWalls(val2.x, val2.z, garageOpening.Opening, out IReadOnlyList openings, out IReadOnlyList wallPlans) || openings == null || wallPlans == null) { throw new InvalidOperationException("The personnel and garage openings could not produce valid non-overlapping room-wall plans."); } Material sharedMaterial = FindBrickMaterial(sourceRoot) ?? CreateFallbackMaterial(); _root = new GameObject("OC_FishWarehouse_InteriorRoom"); _root.transform.SetParent(propertyRoot, false); _root.transform.localPosition = Vector3.zero; _root.transform.localRotation = Quaternion.identity; _root.transform.localScale = Vector3.one; _root.SetActive(false); IReadOnlyList closurePieces = BuildClosurePieces(openings, garageOpening, log); MeshData meshData = BuildRoomMesh(wallPlans, closurePieces); _mesh = new Mesh { name = "OC_FishWarehouse_InteriorRoomMesh" }; _mesh.vertices = Il2CppStructArray.op_Implicit(meshData.Vertices.ToArray()); _mesh.triangles = Il2CppStructArray.op_Implicit(meshData.Triangles.ToArray()); _mesh.RecalculateNormals(); _mesh.RecalculateBounds(); _root.AddComponent().sharedMesh = _mesh; MeshRenderer obj = _root.AddComponent(); ((Renderer)obj).sharedMaterial = sharedMaterial; ((Renderer)obj).receiveShadows = true; ((Renderer)obj).shadowCastingMode = (ShadowCastingMode)1; _root.AddComponent().sharedMesh = _mesh; _openings = openings; return true; } catch (Exception ex) { log?.Invoke("Fish Warehouse authored interior room attachment failed safely: " + ex.Message); Dispose(); return false; } } public bool Activate() { if (!IsReady) { return false; } try { _root.SetActive(true); return true; } catch { _root = null; _mesh = null; _openings = Array.Empty(); return false; } } public void Dispose() { if (_root != null && (Object)(object)_root != (Object)null) { try { _root.SetActive(false); Object.DestroyImmediate((Object)(object)_root); } catch { } } if (_mesh != null && (Object)(object)_mesh != (Object)null) { try { Object.DestroyImmediate((Object)(object)_mesh); } catch { } } _root = null; _mesh = null; _openings = Array.Empty(); } private static MeshData BuildRoomMesh(IReadOnlyList wallPlans, IReadOnlyList closurePieces) { MeshData meshData = new MeshData(); float halfWidth = 11.6f; float halfDepth = 6.9f; float ceilingY = 5.8f; foreach (FishWarehouseWallPlan wallPlan in wallPlans) { foreach (FishWarehouseWallSegment segment in wallPlan.Segments) { AddWallQuad(meshData, segment.Wall, segment.HorizontalMinimum, segment.HorizontalMaximum, segment.Bottom, segment.Top, halfWidth, halfDepth); } } if (!closurePieces.Any((FishWarehouseClosurePiece piece) => piece.Plane == FishWarehouseClosurePlane.CeilingXZ)) { AddCeiling(meshData, halfWidth, halfDepth, ceilingY); } foreach (FishWarehouseClosurePiece closurePiece in closurePieces) { AddClosurePiece(meshData, closurePiece); } return meshData; } private static IReadOnlyList BuildClosurePieces(IReadOnlyList openings, FishWarehouseGarageOpeningProjection garageOpening, Action? log) { List list = new List(); if (FishWarehouseInteriorClosureDefinition.TryPlanCeiling(out FishWarehouseClosurePiece ceiling) && (object)ceiling != null) { list.Add(ceiling); } else { log?.Invoke("Fish Warehouse interior closure: enlarged ceiling could not be planned; falling back to the room-sized ceiling."); } if (FishWarehouseInteriorClosureDefinition.TryPlanPersonnelSeal(openings.FirstOrDefault((FishWarehouseWallOpening opening) => string.Equals(opening.SemanticId, "personnel", StringComparison.Ordinal)), out FishWarehouseClosurePiece seal) && (object)seal != null) { list.Add(seal); } else { log?.Invoke("Fish Warehouse interior closure: personnel seal could not be planned; skipping it safely."); } if (FishWarehouseInteriorClosureDefinition.TryPlanGarageHeader(garageOpening, out FishWarehouseClosurePiece header) && (object)header != null) { list.Add(header); } else { log?.Invoke("Fish Warehouse interior closure: garage header could not be planned; skipping it safely."); } return list; } private static void AddClosurePiece(MeshData data, FishWarehouseClosurePiece piece) { //IL_00c0: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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_00c6: 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_00db: 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_00df: Unknown result type (might be due to invalid IL or missing references) float horizontalMinimum = piece.HorizontalMinimum; float horizontalMaximum = piece.HorizontalMaximum; float verticalMinimum = piece.VerticalMinimum; float verticalMaximum = piece.VerticalMaximum; float planeCoordinate = piece.PlaneCoordinate; Vector3 val = default(Vector3); Vector3 val2 = default(Vector3); Vector3 val3 = default(Vector3); Vector3 val4 = default(Vector3); switch (piece.Plane) { case FishWarehouseClosurePlane.CeilingXZ: ((Vector3)(ref val))..ctor(horizontalMinimum, planeCoordinate, verticalMinimum); ((Vector3)(ref val2))..ctor(horizontalMaximum, planeCoordinate, verticalMinimum); ((Vector3)(ref val3))..ctor(horizontalMaximum, planeCoordinate, verticalMaximum); ((Vector3)(ref val4))..ctor(horizontalMinimum, planeCoordinate, verticalMaximum); break; case FishWarehouseClosurePlane.WallX: ((Vector3)(ref val))..ctor(planeCoordinate, verticalMinimum, horizontalMinimum); ((Vector3)(ref val2))..ctor(planeCoordinate, verticalMinimum, horizontalMaximum); ((Vector3)(ref val3))..ctor(planeCoordinate, verticalMaximum, horizontalMaximum); ((Vector3)(ref val4))..ctor(planeCoordinate, verticalMaximum, horizontalMinimum); break; default: ((Vector3)(ref val))..ctor(horizontalMinimum, verticalMinimum, planeCoordinate); ((Vector3)(ref val2))..ctor(horizontalMaximum, verticalMinimum, planeCoordinate); ((Vector3)(ref val3))..ctor(horizontalMaximum, verticalMaximum, planeCoordinate); ((Vector3)(ref val4))..ctor(horizontalMinimum, verticalMaximum, planeCoordinate); break; } AddQuad(data, val, val2, val3, val4, data.Vertices.Count); AddQuad(data, val4, val3, val2, val, data.Vertices.Count); } private static void AddWallQuad(MeshData data, FishWarehouseInteriorDoorSide side, float start, float end, float bottom, float top, float halfWidth, float halfDepth) { //IL_002a: 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_0040: 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_005d: 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) //IL_0071: 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_008e: 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_00a4: 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_00c1: 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_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) int count = data.Vertices.Count; switch (side) { case FishWarehouseInteriorDoorSide.West: AddQuad(data, new Vector3(0f - halfWidth, bottom, end), new Vector3(0f - halfWidth, bottom, start), new Vector3(0f - halfWidth, top, start), new Vector3(0f - halfWidth, top, end), count); break; case FishWarehouseInteriorDoorSide.East: AddQuad(data, new Vector3(halfWidth, bottom, start), new Vector3(halfWidth, bottom, end), new Vector3(halfWidth, top, end), new Vector3(halfWidth, top, start), count); break; case FishWarehouseInteriorDoorSide.South: AddQuad(data, new Vector3(start, bottom, 0f - halfDepth), new Vector3(end, bottom, 0f - halfDepth), new Vector3(end, top, 0f - halfDepth), new Vector3(start, top, 0f - halfDepth), count); break; default: AddQuad(data, new Vector3(end, bottom, halfDepth), new Vector3(start, bottom, halfDepth), new Vector3(start, top, halfDepth), new Vector3(end, top, halfDepth), count); break; } } private static void AddCeiling(MeshData data, float halfWidth, float halfDepth, float ceilingY) { //IL_0012: 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_0023: 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) int count = data.Vertices.Count; AddQuad(data, new Vector3(0f - halfWidth, ceilingY, 0f - halfDepth), new Vector3(halfWidth, ceilingY, 0f - halfDepth), new Vector3(halfWidth, ceilingY, halfDepth), new Vector3(0f - halfWidth, ceilingY, halfDepth), count); } private static void AddQuad(MeshData data, Vector3 a, Vector3 b, Vector3 c, Vector3 d, int index) { //IL_0006: 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_001e: 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) data.Vertices.Add(a); data.Vertices.Add(b); data.Vertices.Add(c); data.Vertices.Add(d); data.Triangles.Add(index); data.Triangles.Add(index + 1); data.Triangles.Add(index + 2); data.Triangles.Add(index); data.Triangles.Add(index + 2); data.Triangles.Add(index + 3); } private static Transform FindSourceRoot(Transform anchor) { for (Transform val = anchor; val != null; val = val.parent) { if (string.Equals(((Object)val).name, "Fish Warehouse", StringComparison.OrdinalIgnoreCase)) { return val; } } return anchor; } private static Transform? FindEntranceDoor(Transform sourceRoot) { Transform[] array = ((IEnumerable)((Component)sourceRoot).GetComponentsInChildren(true)).Where((Transform transform) => string.Equals(((Object)transform).name, "Industrial Metal Door (Static)", StringComparison.Ordinal)).ToArray(); if (array.Length != 1) { return null; } return array[0]; } private static FishWarehouseTransformSignature ToTransformSignature(Transform transform) { //IL_0001: 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_0017: 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_0032: 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_004d: 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_0063: Unknown result type (might be due to invalid IL or missing references) return new FishWarehouseTransformSignature(new FishWarehouseTransformVector(transform.position.x, transform.position.y, transform.position.z), new FishWarehouseTransformVector(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z), new FishWarehouseTransformVector(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z)); } private static bool IsAlive(Object? value) { try { return value != null && value != (Object)null; } catch { return false; } } private static Material? FindBrickMaterial(Transform sourceRoot) { Renderer[] source = Il2CppArrayBase.op_Implicit(((Component)sourceRoot).GetComponentsInChildren(true)); Renderer? obj = ((IEnumerable)source).FirstOrDefault((Func)((Renderer renderer) => string.Equals(((Object)((Component)renderer).transform).name, "BrickTrim", StringComparison.OrdinalIgnoreCase))); return ((obj != null) ? ((IEnumerable)obj.sharedMaterials).FirstOrDefault((Func)((Material material) => material != null)) : null) ?? source.SelectMany((Renderer renderer) => (IEnumerable)renderer.sharedMaterials).FirstOrDefault((Func)((Material material) => material != null)); } private static Material CreateFallbackMaterial() { //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_0037: Expected O, but got Unknown //IL_005e: 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) Shader obj = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); if (obj == null) { throw new InvalidOperationException("No usable Lit or Standard shader was found for the authored interior room."); } Material val = new Material(obj) { name = "OC_FishWarehouse_InteriorBrickFallback" }; if (val.HasProperty("_BaseColor")) { val.SetColor("_BaseColor", new Color(0.42f, 0.27f, 0.2f, 1f)); } if (val.HasProperty("_Color")) { val.SetColor("_Color", new Color(0.42f, 0.27f, 0.2f, 1f)); } if (val.HasProperty("_Cull")) { val.SetFloat("_Cull", 0f); } val.renderQueue = 2000; val.SetOverrideTag("RenderType", "Opaque"); val.doubleSidedGI = true; return val; } } public sealed class FishWarehouseInteriorShellHost { private GameObject? _root; private int _rendererCount; private int _fallbackMaterialCount; public bool IsReady { get { if (_root != null) { return _rendererCount > 0; } return false; } } public bool TryAttach(Transform anchor, Transform propertyRoot, Action? log = null) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00b2: 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_00dc: Unknown result type (might be due to invalid IL or missing references) if (IsReady) { return true; } if (anchor == null || propertyRoot == null) { return false; } try { Transform val = FindSourceRoot(anchor); string text = BuildHierarchyPath(val); if (!FishWarehouseInteriorShellDefinition.IsFishWarehouseSourcePath(text)) { throw new InvalidOperationException("Fish Warehouse source root was outside the expected path: " + text); } Renderer[] array = Il2CppArrayBase.op_Implicit(((Component)val).GetComponentsInChildren(true)).Where(delegate(Renderer renderer) { if (renderer != null) { MeshFilter component = ((Component)renderer).GetComponent(); if (((component != null) ? component.sharedMesh : null) != null && ((Il2CppArrayBase)(object)renderer.sharedMaterials).Length > 0 && ((IEnumerable)renderer.sharedMaterials).Any((Material material) => material != null)) { string? componentTypeName = ((object)renderer).GetType().FullName ?? ((object)renderer).GetType().Name; MeshFilter component2 = ((Component)renderer).GetComponent(); return FishWarehouseInteriorShellDefinition.IsRendererCloneCandidate(componentTypeName, ((component2 != null) ? component2.sharedMesh : null) != null); } } return false; }).ToArray(); if (array.Length == 0) { throw new InvalidOperationException("No mesh-bearing Renderer/MeshFilter pairs were found below " + text); } _root = new GameObject("OC_FishWarehouse_InteriorShell"); _root.transform.SetParent(propertyRoot, false); _root.transform.localPosition = Vector3.zero; _root.transform.localRotation = Quaternion.identity; _root.transform.localScale = Vector3.one; _root.SetActive(false); Dictionary cloneTransforms = new Dictionary(); Dictionary fallbackMaterials = new Dictionary(); for (int num = 0; num < array.Length; num++) { CloneRenderer(array[num], val, _root.transform, cloneTransforms, fallbackMaterials); } if (_rendererCount == 0) { throw new InvalidOperationException("Fish Warehouse interior shell did not create any renderers"); } log?.Invoke($"Fish Warehouse interior shell prepared from '{text}' ({_rendererCount} render-only mesh renderer(s), fallback materials={_fallbackMaterialCount}, source hierarchy preserved); it will be enabled only while the player is inside."); return true; } catch (Exception ex) { log?.Invoke("Fish Warehouse interior shell attachment failed safely: " + ex.Message); Dispose(); return false; } } public void SetVisible(bool visible) { if (_root != null) { _root.SetActive(visible); } } public void Dispose() { if (_root != null) { try { Object.DestroyImmediate((Object)(object)_root); } catch { } } _root = null; _rendererCount = 0; _fallbackMaterialCount = 0; } private static Transform FindSourceRoot(Transform anchor) { for (Transform val = anchor; val != null; val = val.parent) { if (string.Equals(((Object)val).name, "Fish Warehouse", StringComparison.OrdinalIgnoreCase)) { return val; } } return anchor; } private void CloneRenderer(Renderer source, Transform sourceRoot, Transform shellRoot, Dictionary cloneTransforms, Dictionary fallbackMaterials) { MeshFilter component = ((Component)source).GetComponent(); if (((component != null) ? component.sharedMesh : null) != null) { Transform orCreateCloneTransform = GetOrCreateCloneTransform(((Component)source).transform, sourceRoot, shellRoot, cloneTransforms); Material[] array = CreateDoubleSidedMaterials(Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)source.sharedMaterials), fallbackMaterials); if (array != null) { CreateRendererClone(source, orCreateCloneTransform, component.sharedMesh, array, ""); } } } private void CreateRendererClone(Renderer source, Transform cloneParent, Mesh mesh, Material[] materials, string suffix) { //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_002d: 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_0040: 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: 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_0060: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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) GameObject val = new GameObject("OC_FishWarehouse_Interior_" + ((Object)((Component)source).gameObject).name + suffix) { layer = ((Component)source).gameObject.layer }; val.transform.SetParent(cloneParent, false); val.transform.localPosition = Vector3.zero; val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one; val.AddComponent().sharedMesh = mesh; MeshRenderer obj = val.AddComponent(); ((Renderer)obj).sharedMaterials = Il2CppReferenceArray.op_Implicit(materials); ((Renderer)obj).receiveShadows = source.receiveShadows; ((Renderer)obj).shadowCastingMode = source.shadowCastingMode; _rendererCount++; } private static Transform GetOrCreateCloneTransform(Transform source, Transform sourceRoot, Transform shellRoot, Dictionary cloneTransforms) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Expected O, but got Unknown //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: 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_009f: 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_00c1: Unknown result type (might be due to invalid IL or missing references) if (cloneTransforms.TryGetValue(source, out Transform value)) { return value; } GameObject val = new GameObject("OC_FishWarehouse_InteriorNode_" + ((Object)source).name); if ((Object)(object)source == (Object)(object)sourceRoot) { val.transform.SetParent(shellRoot, true); val.transform.SetPositionAndRotation(source.position, source.rotation); val.transform.localScale = source.lossyScale; } else { Transform orCreateCloneTransform = GetOrCreateCloneTransform((Transform)((source.parent != null && IsDescendantOf(source.parent, sourceRoot)) ? ((object)source.parent) : ((object)sourceRoot)), sourceRoot, shellRoot, cloneTransforms); val.transform.SetParent(orCreateCloneTransform, false); val.transform.localPosition = source.localPosition; val.transform.localRotation = source.localRotation; val.transform.localScale = source.localScale; } val.layer = ((Component)source).gameObject.layer; cloneTransforms[source] = val.transform; return val.transform; } private static bool IsDescendantOf(Transform candidate, Transform ancestor) { Transform val = candidate; int num = 0; while (val != null && num++ < 64) { if ((Object)(object)val == (Object)(object)ancestor) { return true; } val = val.parent; } return false; } private Material[]? CreateDoubleSidedMaterials(Material[] sourceMaterials, Dictionary fallbackMaterials) { Material val = ((IEnumerable)sourceMaterials).FirstOrDefault((Func)((Material material) => material != null)); if (!FishWarehouseInteriorShellDefinition.CanPrepareMaterialSlots(sourceMaterials.Length, sourceMaterials.Count((Material material) => material != null)) || val == null) { return null; } Material[] array = (Material[])(object)new Material[sourceMaterials.Length]; for (int num = 0; num < sourceMaterials.Length; num++) { Material val2 = sourceMaterials[num] ?? val; Material val3; Material value; if (val2.HasProperty("_Cull") || val2.HasProperty("_CullMode")) { val3 = Object.Instantiate(val2); PrepareDoubleSidedMaterial(val3); } else if (fallbackMaterials.TryGetValue(val2, out value)) { val3 = value; } else { Material val4 = CreateFallbackMaterial(val2); if (val4 == null) { return null; } val3 = (fallbackMaterials[val2] = val4); _fallbackMaterialCount++; } array[num] = val3; } return array; } private static Material? CreateFallbackMaterial(Material source) { //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_003b: Expected O, but got Unknown Shader val = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); if (val == null) { return null; } Material val2 = new Material(val) { name = ((Object)source).name + "_OC_DoubleSidedFallback" }; try { val2.CopyPropertiesFromMaterial(source); } catch { } PrepareDoubleSidedMaterial(val2); return val2; } private static void PrepareDoubleSidedMaterial(Material material) { if (material.HasProperty("_Cull")) { material.SetFloat("_Cull", 0f); } if (material.HasProperty("_CullMode")) { material.SetFloat("_CullMode", 0f); } if (material.HasProperty("_Surface")) { material.SetFloat("_Surface", 0f); } if (material.HasProperty("_Mode")) { material.SetFloat("_Mode", 0f); } material.renderQueue = 2000; material.SetOverrideTag("RenderType", "Opaque"); material.doubleSidedGI = true; } private static string BuildHierarchyPath(Transform transform) { Stack stack = new Stack(); Transform val = transform; int num = 0; while (val != null && num++ < 32) { stack.Push(((Object)val).name); val = val.parent; } return string.Join("/", stack); } } public sealed class FishWarehouseLoadingDockHost { private sealed record ClonedDock(GameObject Root, LoadingDock Dock, ParkingLot Parking, VehicleDetector Detector, BoxCollider DetectionCollider, FishWarehouseDockDefinition Definition); private Property? _property; private GameObject? _root; private Il2CppReferenceArray? _previousDocks; private Il2CppReferenceArray? _publishedDocks; private readonly List _docks = new List(); private readonly List _parkingLots = new List(); private readonly List _detectors = new List(); public bool IsReady { get { if (_property != null && _root != null && _publishedDocks != null) { return _docks.Count == FishWarehouseLoadingDockDefinition.Docks.Count; } return false; } } public bool TryAttach(Property property, Action? log = null) { //IL_031f: Unknown result type (might be due to invalid IL or missing references) //IL_033c: 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_00b1: Expected O, but got Unknown if (IsReady) { return true; } if (property == null || !FishWarehouseLoadingDockDefinition.HasValidLayout()) { return false; } GameObject val = null; List list = new List(); bool flag = false; try { Property? obj = FindProperty("barn"); LoadingDock[] array = ((obj == null) ? null : ((IEnumerable)obj.LoadingDocks)?.Where((LoadingDock dock) => dock != null && dock.InputSlots.Count == 0 && dock.OutputSlots.Count == 0).Take(2).ToArray()) ?? Array.Empty(); if (array.Length != 2) { log?.Invoke("Fish Warehouse loading dock setup failed safely: Barn did not expose two complete zero-slot loading docks."); return false; } val = new GameObject("OC_FishWarehouse_LoadingDocks"); val.transform.SetParent(((Component)property).transform, false); val.SetActive(false); for (int num = 0; num < FishWarehouseLoadingDockDefinition.Docks.Count; num++) { list.Add(CloneAndConfigure(array[num], FishWarehouseLoadingDockDefinition.Docks[num], property, val.transform)); } int validCount = list.Count((ClonedDock entry) => ValidateClone(entry, property)); if (!FishWarehouseLoadingDockDefinition.CanPublish(list.Count, validCount)) { throw new InvalidOperationException("Both Fish Warehouse loading docks did not pass staged validation."); } Il2CppReferenceArray val2 = new Il2CppReferenceArray((long)list.Count); for (int num2 = 0; num2 < list.Count; num2++) { ((Il2CppArrayBase)(object)val2)[num2] = list[num2].Dock; } _previousDocks = property.LoadingDocks; flag = true; property.LoadingDocks = val2; if (property.LoadingDocks == null || ((Il2CppArrayBase)(object)property.LoadingDocks).Length != 2 || (Object)(object)((Il2CppArrayBase)(object)property.LoadingDocks)[0] != (Object)(object)list[0].Dock || (Object)(object)((Il2CppArrayBase)(object)property.LoadingDocks)[1] != (Object)(object)list[1].Dock) { property.LoadingDocks = _previousDocks; throw new InvalidOperationException("Fish Warehouse did not retain the atomic loading-dock publication."); } val.SetActive(true); _property = property; _root = val; _publishedDocks = val2; _docks.AddRange(list.Select((ClonedDock entry) => entry.Dock)); _parkingLots.AddRange(list.Select((ClonedDock entry) => entry.Parking)); _detectors.AddRange(list.Select((ClonedDock entry) => entry.Detector)); log?.Invoke("Fish Warehouse native loading docks enabled (count=2, indices=0,1, source=barn, offStreet=True)."); return true; } catch (Exception ex) { if (flag) { property.LoadingDocks = _previousDocks; } foreach (ClonedDock item in list) { DeregisterIfOwned(item.Dock, ParseGuid(item.Definition.DockGuid)); DeregisterIfOwned(item.Parking, ParseGuid(item.Definition.ParkingLotGuid)); } if (val != null) { Object.Destroy((Object)(object)val); } Clear(); log?.Invoke("Fish Warehouse loading dock setup failed safely: " + ex.Message); return false; } } public bool CanUnload() { if (!IsReady || _property == null) { return true; } DeliveryInstance activeDelivery = (NetworkSingleton.InstanceExists ? NetworkSingleton.Instance.GetDelivery(_property) : null); return FishWarehouseLoadingDockDefinition.CanUnload(_docks.Select((LoadingDock dock) => new FishWarehouseDockOccupancySnapshot(dock.DynamicOccupant != null, dock.StaticOccupant != null, activeDelivery != null && (Object)(object)activeDelivery.LoadingDock == (Object)(object)dock))); } public void Dispose() { //IL_0090: 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) if (!CanUnload()) { return; } if (_property != null && _publishedDocks != null && _property.LoadingDocks == _publishedDocks) { _property.LoadingDocks = _previousDocks; } foreach (VehicleDetector detector in _detectors) { detector.Clear(); } for (int i = 0; i < _docks.Count; i++) { DeregisterIfOwned(_docks[i], ParseGuid(FishWarehouseLoadingDockDefinition.Docks[i].DockGuid)); DeregisterIfOwned(_parkingLots[i], ParseGuid(FishWarehouseLoadingDockDefinition.Docks[i].ParkingLotGuid)); } if (_root != null) { Object.Destroy((Object)(object)_root); } Clear(); } private static ClonedDock CloneAndConfigure(LoadingDock source, FishWarehouseDockDefinition definition, Property property, Transform stagingRoot) { //IL_0679: Unknown result type (might be due to invalid IL or missing references) //IL_068f: Unknown result type (might be due to invalid IL or missing references) //IL_02b3: Unknown result type (might be due to invalid IL or missing references) //IL_02b8: 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_02c5: 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_02d0: 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_0319: 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_03b9: 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_03c6: Unknown result type (might be due to invalid IL or missing references) //IL_03db: Unknown result type (might be due to invalid IL or missing references) //IL_03e0: Unknown result type (might be due to invalid IL or missing references) //IL_03e5: Unknown result type (might be due to invalid IL or missing references) //IL_03f7: Unknown result type (might be due to invalid IL or missing references) //IL_03fb: Unknown result type (might be due to invalid IL or missing references) //IL_0400: Unknown result type (might be due to invalid IL or missing references) //IL_0405: Unknown result type (might be due to invalid IL or missing references) //IL_041a: 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_043a: Unknown result type (might be due to invalid IL or missing references) //IL_043f: Unknown result type (might be due to invalid IL or missing references) //IL_0443: Unknown result type (might be due to invalid IL or missing references) //IL_0448: Unknown result type (might be due to invalid IL or missing references) //IL_044d: Unknown result type (might be due to invalid IL or missing references) //IL_0592: Unknown result type (might be due to invalid IL or missing references) //IL_0594: Unknown result type (might be due to invalid IL or missing references) //IL_05ac: Unknown result type (might be due to invalid IL or missing references) //IL_05d5: Unknown result type (might be due to invalid IL or missing references) GameObject cloneRoot = null; try { ParkingLot val = source.Parking ?? throw new InvalidOperationException("Barn loading dock has no parking lot."); VehicleDetector val2 = source.VehicleDetector ?? throw new InvalidOperationException("Barn loading dock has no vehicle detector."); Il2CppArrayBase val3 = val.ParkingSpots?.ToArray(); ParkingSpot[] array = ((val3 != null) ? Il2CppArrayBase.op_Implicit(val3) : Array.Empty()); if (array.Length == 0) { throw new InvalidOperationException("Barn loading dock has no parking spots."); } Transform sourceRoot = FindSmallestCompleteAncestor(source, val, val2, array); string relativePath = GetRelativePath(sourceRoot, ((Component)source).transform); string relativePath2 = GetRelativePath(sourceRoot, ((Component)val).transform); string relativePath3 = GetRelativePath(sourceRoot, ((Component)val2).transform); string[] source2 = array.Select((ParkingSpot spot) => GetRelativePath(sourceRoot, ((Component)spot).transform)).ToArray(); cloneRoot = Object.Instantiate(((Component)sourceRoot).gameObject, stagingRoot); LoadingDock dock = FindAtPath(cloneRoot.transform, relativePath); ParkingLot parking = FindAtPath(cloneRoot.transform, relativePath2); VehicleDetector detector = FindAtPath(cloneRoot.transform, relativePath3); ParkingSpot[] source3 = source2.Select((string path) => FindAtPath(cloneRoot.transform, path)).ToArray(); if (dock == null || parking == null || detector == null || source3.Any((ParkingSpot spot) => spot == null) || (Object)(object)dock == (Object)(object)source || (Object)(object)parking == (Object)(object)val || (Object)(object)detector == (Object)(object)val2) { throw new InvalidOperationException("Cloned Barn loading dock retained a source reference."); } ParkingSpot[] array2 = source3.Select((ParkingSpot spot) => spot).ToArray(); IGUIDRegisterable[] array3 = (from component in (IEnumerable)cloneRoot.GetComponentsInChildren(true) select ((Il2CppObjectBase)component).TryCast() into component where component != null select component).ToArray(); if (array3.Length != 2 || !array3.Any((IGUIDRegisterable registerable) => ((Il2CppObjectBase)registerable).Pointer == ((Il2CppObjectBase)dock).Pointer) || !array3.Any((IGUIDRegisterable registerable) => ((Il2CppObjectBase)registerable).Pointer == ((Il2CppObjectBase)parking).Pointer)) { throw new InvalidOperationException("Cloned Barn loading dock exposed unexpected GUID-bearing components."); } Guid val4 = ParseGuid(definition.DockGuid); Guid val5 = ParseGuid(definition.ParkingLotGuid); if (GUIDManager.IsGUIDAlreadyRegistered(val4) || GUIDManager.IsGUIDAlreadyRegistered(val5)) { throw new InvalidOperationException("Fish Warehouse loading dock GUID is already registered."); } dock.BakedGUID = definition.DockGuid; parking.BakedGUID = definition.ParkingLotGuid; dock.SetGUID(val4); parking.SetGUID(val5); dock.ParentProperty = property; if (dock.InputSlots.Count != 0 || dock.OutputSlots.Count != 0) { throw new InvalidOperationException("Cloned Barn loading dock did not preserve zero slots."); } ParkingSpot[] array4 = array2; for (int num = 0; num < array4.Length; num++) { array4[num].ParentLot = parking; } Transform val6 = array2[0].AlignmentPoint ?? throw new InvalidOperationException("Cloned parking spot has no alignment point."); Vector3 val7 = ((Component)property).transform.TransformPoint(new Vector3(definition.CenterX, definition.CenterY, definition.CenterZ)); Quaternion val8 = ((Component)property).transform.rotation * Quaternion.Euler(0f, definition.YawDegrees, 0f); cloneRoot.transform.rotation = val8 * Quaternion.Inverse(val6.rotation) * cloneRoot.transform.rotation; Transform transform = cloneRoot.transform; transform.position += val7 - val6.position; Collider[] runtimeReferences = ((IEnumerable)detector.detectionColliders)?.ToArray() ?? Array.Empty(); Collider[] hierarchyColliders = ((IEnumerable)((Component)detector).GetComponentsInChildren(true)).ToArray(); if (!FishWarehouseLoadingDockDefinition.TryBindDetectorCollider((IReadOnlyCollection)(object)runtimeReferences, (IReadOnlyCollection)(object)hierarchyColliders, (Collider collider) => ((Il2CppObjectBase)collider).TryCast(), (Collider collider) => IsChildOf(cloneRoot.transform, ((Component)collider).transform) && IsChildOf(((Component)detector).transform, ((Component)collider).transform), out BoxCollider selected, out FishWarehouseDetectorColliderBindingDiagnostics diagnostics) || selected == null) { throw new InvalidOperationException($"Cloned vehicle detector did not expose one cloned BoxCollider (runtime references={diagnostics.RuntimeReferenceCount}, hierarchy colliders={diagnostics.HierarchyColliderCount}, owned BoxColliders={diagnostics.OwnedBoxColliderCount})."); } BoxCollider val9 = selected; detector.detectionColliders = new Il2CppReferenceArray(1L); ((Il2CppArrayBase)(object)detector.detectionColliders)[0] = (Collider)(object)val9; ((Component)detector).transform.SetPositionAndRotation(val7, val8); val9.center = new Vector3(0f, 1.4f, 0f); val9.size = new Vector3(definition.HalfWidth * 2f, 2.8f, definition.HalfLength * 2f); ((Collider)val9).isTrigger = true; ((Object)cloneRoot).name = definition.Name; ((Object)dock).name = definition.Name; cloneRoot.SetActive(true); return new ClonedDock(cloneRoot, dock, parking, detector, val9, definition); } catch { if (cloneRoot != null) { LoadingDock componentInChildren = cloneRoot.GetComponentInChildren(true); ParkingLot componentInChildren2 = cloneRoot.GetComponentInChildren(true); if (componentInChildren != null) { DeregisterIfOwned(componentInChildren, ParseGuid(definition.DockGuid)); } if (componentInChildren2 != null) { DeregisterIfOwned(componentInChildren2, ParseGuid(definition.ParkingLotGuid)); } Object.Destroy((Object)(object)cloneRoot); } throw; } } private static bool ValidateClone(ClonedDock entry, Property property) { //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_0032: 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_005a: 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_023d: Unknown result type (might be due to invalid IL or missing references) //IL_0254: Unknown result type (might be due to invalid IL or missing references) //IL_0276: Unknown result type (might be due to invalid IL or missing references) FishWarehouseDockDefinition definition = entry.Definition; Guid gUID = entry.Dock.GUID; if (((Guid)(ref gUID)).Equals(ParseGuid(definition.DockGuid))) { gUID = entry.Parking.GUID; if (((Guid)(ref gUID)).Equals(ParseGuid(definition.ParkingLotGuid)) && (Object)(object)entry.Dock.ParentProperty == (Object)(object)property && entry.Dock.InputSlots.Count == 0 && entry.Dock.OutputSlots.Count == 0 && entry.Parking.ParkingSpots != null && entry.Parking.ParkingSpots.Count > 0 && ((IEnumerable)entry.Parking.ParkingSpots.ToArray()).All((ParkingSpot spot) => (Object)(object)spot.ParentLot == (Object)(object)entry.Parking && IsChildOf(entry.Root.transform, ((Component)spot).transform)) && IsChildOf(entry.Root.transform, ((Component)entry.Dock).transform) && IsChildOf(entry.Root.transform, ((Component)entry.Parking).transform) && IsChildOf(entry.Root.transform, ((Component)entry.Detector).transform) && (Object)(object)entry.Dock.Parking == (Object)(object)entry.Parking && (Object)(object)entry.Dock.VehicleDetector == (Object)(object)entry.Detector && ((Il2CppArrayBase)(object)entry.Detector.detectionColliders).Count == 1 && (Object)(object)((Il2CppArrayBase)(object)entry.Detector.detectionColliders)[0] == (Object)(object)entry.DetectionCollider && entry.DetectionCollider.center == new Vector3(0f, 1.4f, 0f)) { return entry.DetectionCollider.size == new Vector3(definition.HalfWidth * 2f, 2.8f, definition.HalfLength * 2f); } } return false; } private static Transform FindSmallestCompleteAncestor(LoadingDock dock, ParkingLot parking, VehicleDetector detector, IReadOnlyList spots) { Transform candidate; for (candidate = ((Component)dock).transform; candidate != null; candidate = candidate.parent) { if (IsChildOf(candidate, ((Component)parking).transform) && IsChildOf(candidate, ((Component)detector).transform) && !spots.Any((ParkingSpot spot) => !IsChildOf(candidate, ((Component)spot).transform))) { if (((Component)candidate).GetComponentInChildren(true) != null || ((Component)candidate).GetComponentInChildren(true) != null) { throw new InvalidOperationException("Barn loading dock clone boundary includes a Property or NetworkObject."); } return candidate; } } throw new InvalidOperationException("Barn loading dock has no complete clone boundary."); } private static T? FindAtPath(Transform root, string path) where T : Component { Transform val = (Transform)(string.IsNullOrEmpty(path) ? ((object)root) : ((object)root.Find(path))); if (val != null) { return ((Component)val).GetComponent(); } return default(T); } private static string GetRelativePath(Transform root, Transform child) { Stack stack = new Stack(); Transform val = child; while (val != null && (Object)(object)val != (Object)(object)root) { stack.Push(((Object)val).name); val = val.parent; } if ((Object)(object)child != (Object)(object)root && stack.Count == 0) { throw new InvalidOperationException("Barn loading dock reference lies outside the clone boundary."); } return string.Join("/", stack); } private static bool IsChildOf(Transform root, Transform candidate) { if (!((Object)(object)candidate == (Object)(object)root)) { return candidate.IsChildOf(root); } return true; } private static Property? FindProperty(string code) { foreach (Property item in Property.Properties.ToArray()) { if (string.Equals(item.PropertyCode, code, StringComparison.OrdinalIgnoreCase)) { return item; } } return null; } private static Guid ParseGuid(string value) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) return new Guid(value); } private static void DeregisterIfOwned(LoadingDock dock, Guid guid) { //IL_0000: 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) if (GUIDManager.IsGUIDAlreadyRegistered(guid) && (Object)(object)GUIDManager.GetObject(guid) == (Object)(object)dock) { GUIDManager.DeregisterObject(((Il2CppObjectBase)dock).TryCast()); } } private static void DeregisterIfOwned(ParkingLot parking, Guid guid) { //IL_0000: 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) if (GUIDManager.IsGUIDAlreadyRegistered(guid) && (Object)(object)GUIDManager.GetObject(guid) == (Object)(object)parking) { GUIDManager.DeregisterObject(((Il2CppObjectBase)parking).TryCast()); } } private void Clear() { _property = null; _root = null; _previousDocks = null; _publishedDocks = null; _docks.Clear(); _parkingLots.Clear(); _detectors.Clear(); } } internal static class FishWarehouseManagementCanvasPatch { private static Property? _targetProperty; private static bool _failureReported; public static void Apply(Harmony harmony) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(ManagementWorldspaceCanvas), "GetConfigurablesToShow", (Type[])null, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(FishWarehouseManagementCanvasPatch), "AlignCurrentProperty", (Type[])null, (Type[])null); if ((object)methodInfo == null || (object)methodInfo2 == null) { throw new MissingMethodException("Could not resolve the management canvas configurable filter patch surface."); } harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static void SetTargetProperty(Property? property) { _targetProperty = property; if (property == null) { _failureReported = false; } } private static void AlignCurrentProperty(ManagementWorldspaceCanvas __instance) { try { Property targetProperty = _targetProperty; if (targetProperty != null && __instance != null) { Property currentProperty = __instance.CurrentProperty; string canvasPropertyCode = ((currentProperty != null) ? currentProperty.PropertyCode : null) ?? string.Empty; Player local = Player.Local; object obj; if (local == null) { obj = null; } else { Property currentProperty2 = local.CurrentProperty; obj = ((currentProperty2 != null) ? currentProperty2.PropertyCode : null); } if (obj == null) { obj = string.Empty; } string playerPropertyCode = (string)obj; if (FishWarehouseManagementEligibilityDiagnostic.ShouldAlignCanvas(__instance.IsOpen, canvasPropertyCode, playerPropertyCode, targetProperty.IsOwned)) { __instance.CurrentProperty = targetProperty; } } } catch (Exception value) { if (!_failureReported) { _failureReported = true; MelonLogger.Warning($"[Organized Crime] Fish Warehouse management canvas alignment failed safely: {value}"); } } } } public static class FishWarehouseManagementEligibilityDiagnostic { private const string PropertyCode = "oc_fishwarehouse"; public static bool ShouldAlignCanvas(bool isOpen, string canvasPropertyCode, string playerPropertyCode, bool targetOwned) { if (isOpen && targetOwned && string.Equals(playerPropertyCode, "oc_fishwarehouse", StringComparison.OrdinalIgnoreCase)) { return !string.Equals(canvasPropertyCode, "oc_fishwarehouse", StringComparison.OrdinalIgnoreCase); } return false; } public static bool ShouldCreatePropertyWorldspaceUiContainer(bool targetOwned, bool hasContainer) { if (targetOwned) { return !hasContainer; } return false; } } public sealed class FishWarehouseNativeEmployeeReplayAdapter : IFishWarehouseNativeEmployeeReplayAdapter { private readonly PropertyData _propertyData; private readonly Property _property; private readonly EmployeeManager? _employeeManager; private readonly HashSet _fallbackConfigurationApplied = new HashSet(StringComparer.Ordinal); private readonly HashSet _inventoryRestored = new HashSet(StringComparer.Ordinal); public IReadOnlyCollection InventoryRestoredEmployeeGuids => _inventoryRestored; public FishWarehouseNativeEmployeeReplayAdapter(PropertyData propertyData, Property property, EmployeeManager? employeeManager = null, IReadOnlyCollection? inventoryRestoredEmployeeGuids = null) { _propertyData = propertyData ?? throw new ArgumentNullException("propertyData"); _property = property ?? throw new ArgumentNullException("property"); _employeeManager = employeeManager; foreach (string item in (IEnumerable)(((object)inventoryRestoredEmployeeGuids) ?? ((object)Array.Empty()))) { _inventoryRestored.Add(CanonicalGuid(item)); } } public bool TryIsGuidRegistered(string savedGuid, out bool isRegistered, out string? failureReason) { isRegistered = false; failureReason = null; if (!TryInvokeGuidInterop(savedGuid, (Func)GUIDManager.IsGUIDAlreadyRegistered, out isRegistered)) { failureReason = "malformed-guid (guid=" + savedGuid + ")"; return false; } return true; } public FishWarehouseEmployeePrimaryLoadResult TryLoadPrimary(FishWarehouseEmployeeReplayDescriptor descriptor, out string? failureReason) { failureReason = null; if (!TryFindCapturedData(descriptor, out DynamicSaveData data, out failureReason)) { return FishWarehouseEmployeePrimaryLoadResult.LoaderUnavailable; } try { LoadManager instance = Singleton.Instance; NPCLoader val = ((instance != null) ? instance.GetNPCLoader(((SaveData)data).DataType) : null); if (val == null) { failureReason = "missing-npc-loader (type=" + ((SaveData)data).DataType + ")"; return FishWarehouseEmployeePrimaryLoadResult.LoaderUnavailable; } if (val != null) { ((DynamicLoader)val).Load(data); } return FishWarehouseEmployeePrimaryLoadResult.Started; } catch (Exception ex) { failureReason = "npc-loader-failure (type=" + descriptor.DataType + "): " + ex.Message; return FishWarehouseEmployeePrimaryLoadResult.Failed; } } public FishWarehouseEmployeeObservationResult TryObserveEmployee(string savedGuid, out FishWarehouseEmployeeReplayObservedState? observation, out string? failureReason) { //IL_0068: 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_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) //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) //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_0109: 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_0115: 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_0121: 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) observation = null; failureReason = null; if (!TryIsGuidRegistered(savedGuid, out var isRegistered, out failureReason)) { return FishWarehouseEmployeeObservationResult.Failed; } if (!isRegistered) { return FishWarehouseEmployeeObservationResult.Pending; } if (!TryInvokeGuidInterop(savedGuid, (Guid nativeGuid) => GUIDManager.GetObject(nativeGuid), out var result)) { failureReason = "malformed-guid (guid=" + savedGuid + ")"; return FishWarehouseEmployeeObservationResult.Failed; } if (!IsAlive((Object?)(object)result)) { return FishWarehouseEmployeeObservationResult.Pending; } try { Transform transform = ((Component)result).transform; Vector3 position = transform.position; Quaternion rotation = transform.rotation; PackagerConfiguration configuration = result.configuration; MoveItemData val = (IsAlive((Object?)(object)((Employee)result).MoveItemBehaviour) ? ((Employee)result).MoveItemBehaviour.GetSaveData() : null); FishWarehouseEmployeeMoveItemState moveItem = ((val == null) ? null : new FishWarehouseEmployeeMoveItemState(val.SourceGUID, val.DestinationGUID, val.TemplateItemJSON, val.GrabbedItemQuantity)); string? guid = ((object)((NPC)result).GUID/*cast due to .constrained prefix*/).ToString(); string iD = ((NPC)result).ID; string firstName = ((NPC)result).FirstName; string lastName = ((NPC)result).LastName; bool isMale = ((Employee)result).IsMale; int appearanceIndex = ((Employee)result).AppearanceIndex; float x = position.x; float y = position.y; float z = position.z; float x2 = rotation.x; float y2 = rotation.y; float z2 = rotation.z; float w = rotation.w; Property assignedProperty = ((Employee)result).AssignedProperty; string propertyCode = ((assignedProperty != null) ? assignedProperty.PropertyCode : null) ?? string.Empty; bool paidForToday = ((Employee)result).PaidForToday; object obj; if (configuration == null) { obj = null; } else { ObjectField home = configuration.Home; if (home == null) { obj = null; } else { BuildableItem selectedObject = home.SelectedObject; obj = ((selectedObject != null) ? ((object)selectedObject.GUID/*cast due to .constrained prefix*/).ToString() : null); } } if (obj == null) { obj = string.Empty; } observation = new FishWarehouseEmployeeReplayObservedState(IsAlive: true, guid, "Packager", iD, firstName, lastName, isMale, appearanceIndex, x, y, z, x2, y2, z2, w, propertyCode, paidForToday, moveItem, (string)obj, ReadStationGuids(result)); return FishWarehouseEmployeeObservationResult.Ready; } catch (Exception ex) { failureReason = "employee-observation-failure (guid=" + savedGuid + "): " + ex.Message; return FishWarehouseEmployeeObservationResult.Failed; } } public FishWarehouseEmployeeFallbackCreationResult TryCreateFallback(FishWarehouseEmployeeReplayDescriptor descriptor, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason) { //IL_008c: 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) failureReason = null; if (!TryIsGuidRegistered(descriptor.Guid, out var isRegistered, out failureReason)) { return FishWarehouseEmployeeFallbackCreationResult.Failed; } if (isRegistered) { failureReason = "fallback creation rejected existing GUID (guid=" + descriptor.Guid + ")"; return FishWarehouseEmployeeFallbackCreationResult.Failed; } EmployeeManager val = _employeeManager ?? NetworkSingleton.Instance; if (!IsAlive((Object?)(object)val)) { failureReason = "fallback creation rejected because EmployeeManager was unavailable"; return FishWarehouseEmployeeFallbackCreationResult.Failed; } try { val.RpcLogic___CreateEmployee_311954683(_property, (EEmployeeType)1, expected.FirstName, expected.LastName, expected.Id, expected.IsMale, expected.AppearanceIndex, new Vector3(expected.PositionX, expected.PositionY, expected.PositionZ), new Quaternion(expected.RotationX, expected.RotationY, expected.RotationZ, expected.RotationW), expected.Guid); return FishWarehouseEmployeeFallbackCreationResult.Started; } catch (Exception ex) { failureReason = "fallback creation failed: " + ex.Message; return FishWarehouseEmployeeFallbackCreationResult.Failed; } } public FishWarehouseEmployeeFallbackConfigurationResult TryConfigureFallback(FishWarehouseEmployeeReplayDescriptor descriptor, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason) { failureReason = null; string item = CanonicalGuid(descriptor.Guid); if (_fallbackConfigurationApplied.Contains(item)) { return FishWarehouseEmployeeFallbackConfigurationResult.Configured; } if (!TryIsGuidRegistered(descriptor.Guid, out var isRegistered, out failureReason)) { return FishWarehouseEmployeeFallbackConfigurationResult.Failed; } if (!isRegistered || !TryInvokeGuidInterop(descriptor.Guid, (Guid nativeGuid) => GUIDManager.GetObject(nativeGuid), out var result)) { return FishWarehouseEmployeeFallbackConfigurationResult.Pending; } if (!IsAlive((Object?)(object)result)) { return FishWarehouseEmployeeFallbackConfigurationResult.Pending; } if (!TryFindCapturedData(descriptor, out DynamicSaveData data, out failureReason) || !TryReadPackagerConfiguration(data, out PackagerConfigurationData configuration, out failureReason)) { return FishWarehouseEmployeeFallbackConfigurationResult.Failed; } try { PackagerConfiguration configuration2 = result.configuration; ObjectField val = ((configuration2 != null) ? configuration2.Home : null); ObjectListField val2 = ((configuration2 != null) ? configuration2.Stations : null); RouteListField val3 = ((configuration2 != null) ? configuration2.Routes : null); if (GetFallbackConfigurationSurfaceResult(configuration2 != null, val != null, val2 != null, val3 != null) == FishWarehouseEmployeeFallbackConfigurationResult.Pending) { return FishWarehouseEmployeeFallbackConfigurationResult.Pending; } ((Employee)result).PaidForToday = expected.PaidForToday; val.Load(configuration.Bed); val2.Load(configuration.Stations); val3.Load(configuration.Routes); if (!AllConfigurationGuidsResolved(expected, configuration)) { return FishWarehouseEmployeeFallbackConfigurationResult.Pending; } _fallbackConfigurationApplied.Add(item); return FishWarehouseEmployeeFallbackConfigurationResult.Configured; } catch (Exception ex) { failureReason = "fallback configuration failed: " + ex.Message; return FishWarehouseEmployeeFallbackConfigurationResult.Failed; } } public FishWarehouseEmployeeActiveMoveRestoreResult TryRestoreActiveMove(FishWarehouseEmployeeReplayDescriptor descriptor, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason) { //IL_0258: Unknown result type (might be due to invalid IL or missing references) //IL_0269: Unknown result type (might be due to invalid IL or missing references) //IL_026e: Unknown result type (might be due to invalid IL or missing references) //IL_0278: Expected O, but got Unknown failureReason = null; if ((object)expected.MoveItem == null) { return FishWarehouseEmployeeActiveMoveRestoreResult.NotRequired; } if (!TryIsGuidRegistered(descriptor.Guid, out var isRegistered, out failureReason)) { return FishWarehouseEmployeeActiveMoveRestoreResult.Failed; } if (!isRegistered || !TryInvokeGuidInterop(descriptor.Guid, (Guid nativeGuid) => GUIDManager.GetObject(nativeGuid), out var result) || !IsAlive((Object?)(object)result)) { return FishWarehouseEmployeeActiveMoveRestoreResult.Pending; } Packager val = result; if (!Guid.TryParse(expected.MoveItem.SourceGuid, out var result2) || !Guid.TryParse(expected.MoveItem.DestinationGuid, out var result3)) { failureReason = "active-move-malformed-guid (employee=" + descriptor.Guid + ")"; return FishWarehouseEmployeeActiveMoveRestoreResult.Failed; } if (!IsGuidRegistered(expected.MoveItem.SourceGuid) || !IsGuidRegistered(expected.MoveItem.DestinationGuid)) { failureReason = "active-move-endpoints-pending (employee=" + descriptor.Guid + ")"; return FishWarehouseEmployeeActiveMoveRestoreResult.Pending; } try { if (IsAlive((Object?)(object)((Employee)val).MoveItemBehaviour)) { MoveItemData saveData = ((Employee)val).MoveItemBehaviour.GetSaveData(); if (saveData != null) { if (string.Equals(saveData.SourceGUID, expected.MoveItem.SourceGuid, StringComparison.OrdinalIgnoreCase) && string.Equals(saveData.DestinationGUID, expected.MoveItem.DestinationGuid, StringComparison.OrdinalIgnoreCase) && string.Equals(saveData.TemplateItemJSON, expected.MoveItem.TemplateItemJson, StringComparison.Ordinal) && saveData.GrabbedItemQuantity == expected.MoveItem.GrabbedItemQuantity) { return FishWarehouseEmployeeActiveMoveRestoreResult.Restored; } failureReason = "active-move-existing-mismatch (employee=" + descriptor.Guid + ")"; return FishWarehouseEmployeeActiveMoveRestoreResult.Failed; } } if (!_inventoryRestored.Contains(CanonicalGuid(descriptor.Guid))) { DeserializedItemSet val2 = default(DeserializedItemSet); if (string.IsNullOrWhiteSpace(expected.InventoryJson) || !ItemSet.TryDeserialize(expected.InventoryJson, ref val2) || val2 == null || ((NPC)val).Inventory == null || ((NPC)val).Inventory.ItemSlots == null) { failureReason = "active-move-inventory-unavailable (employee=" + descriptor.Guid + ")"; return FishWarehouseEmployeeActiveMoveRestoreResult.Failed; } val2.LoadTo(((NPC)val).Inventory.ItemSlots); _inventoryRestored.Add(CanonicalGuid(descriptor.Guid)); } if (!IsAlive((Object?)(object)((Employee)val).MoveItemBehaviour)) { return FishWarehouseEmployeeActiveMoveRestoreResult.Pending; } ((Employee)val).MoveItemBehaviour.Load(new MoveItemData(expected.MoveItem.TemplateItemJson, expected.MoveItem.GrabbedItemQuantity, new Guid(result2.ToString("D")), new Guid(result3.ToString("D")))); return FishWarehouseEmployeeActiveMoveRestoreResult.Restored; } catch (Exception ex) { failureReason = "active-move-restore-failed (employee=" + descriptor.Guid + "): " + ex.Message; return FishWarehouseEmployeeActiveMoveRestoreResult.Failed; } } internal static FishWarehouseEmployeeFallbackConfigurationResult GetFallbackConfigurationSurfaceResult(bool configurationAvailable, bool homeAvailable, bool stationsAvailable, bool routesAvailable) { if (!(configurationAvailable && homeAvailable && stationsAvailable && routesAvailable)) { return FishWarehouseEmployeeFallbackConfigurationResult.Pending; } return FishWarehouseEmployeeFallbackConfigurationResult.Configured; } private static bool AllConfigurationGuidsResolved(FishWarehouseEmployeeReplayExpectedState expected, PackagerConfigurationData configuration) { if (!IsGuidRegistered(expected.HomeGuid)) { return false; } foreach (string stationGuid in expected.StationGuids) { if (!IsGuidRegistered(stationGuid)) { return false; } } RouteListData routes = configuration.Routes; List val = ((routes != null) ? routes.Routes : null); if (val != null) { Enumerator enumerator2 = val.GetEnumerator(); while (enumerator2.MoveNext()) { AdvancedTransitRouteData current = enumerator2.Current; if (current != null && (!IsGuidRegistered(current.SourceGUID) || !IsGuidRegistered(current.DestinationGUID))) { return false; } } } return true; } private static bool IsGuidRegistered(string? guid) { if (string.IsNullOrWhiteSpace(guid)) { return true; } bool result; return TryInvokeGuidInterop(guid, (Func)GUIDManager.IsGUIDAlreadyRegistered, out result) && result; } internal static bool TryInvokeGuidInterop(string savedGuid, Func interopCall, out T result) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!Guid.TryParse(savedGuid, out var result2)) { result = default(T); return false; } result = interopCall(new Guid(result2.ToString())); return true; } private bool TryFindCapturedData(FishWarehouseEmployeeReplayDescriptor descriptor, out DynamicSaveData data, out string? failureReason) { data = null; failureReason = null; if (!Guid.TryParse(descriptor.Guid, out var result)) { failureReason = "malformed-guid (guid=" + descriptor.Guid + ")"; return false; } foreach (DynamicSaveData item in (Il2CppArrayBase)(object)_propertyData.Employees) { if (TryReadGuid(item.BaseData, out var guid) && !(guid != result)) { if (!string.Equals(((SaveData)item).DataType, descriptor.DataType, StringComparison.Ordinal)) { failureReason = "captured-data-type-mismatch (guid=" + descriptor.Guid + ")"; return false; } data = item; return true; } } failureReason = "captured-employee-payload-missing (guid=" + descriptor.Guid + ")"; return false; } private static bool TryReadGuid(string baseData, out Guid guid) { guid = default(Guid); try { using JsonDocument jsonDocument = JsonDocument.Parse(baseData); JsonElement value; return jsonDocument.RootElement.TryGetProperty("GUID", out value) && value.ValueKind == JsonValueKind.String && Guid.TryParse(value.GetString(), out guid); } catch (JsonException) { return false; } } private static bool TryReadPackagerConfiguration(DynamicSaveData data, out PackagerConfigurationData? configuration, out string? failureReason) { configuration = null; failureReason = null; Enumerator enumerator = data.AdditionalDatas.GetEnumerator(); while (enumerator.MoveNext()) { AdditionalData current = enumerator.Current; if (!string.Equals(current.Name, "Configuration", StringComparison.Ordinal)) { continue; } try { configuration = JsonUtility.FromJson(current.Contents); if (configuration == null) { failureReason = "PackagerConfigurationData was unavailable"; return false; } return true; } catch (Exception ex) { failureReason = "PackagerConfigurationData parse failed: " + ex.Message; return false; } } failureReason = "PackagerConfigurationData was unavailable"; return false; } private static string CanonicalGuid(string savedGuid) { if (!Guid.TryParse(savedGuid, out var result)) { return savedGuid; } return result.ToString("D"); } private static IReadOnlyList ReadStationGuids(Packager packager) { //IL_004a: 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) PackagerConfiguration configuration = packager.configuration; object obj; if (configuration == null) { obj = null; } else { ObjectListField stations = configuration.Stations; obj = ((stations != null) ? stations.SelectedObjects : null); } List val = (List)obj; if (val == null) { return Array.Empty(); } List list = new List(val.Count); for (int i = 0; i < val.Count; i++) { BuildableItem val2 = val[i]; if (IsAlive((Object?)(object)val2)) { list.Add(((object)val2.GUID/*cast due to .constrained prefix*/).ToString()); } } return list; } private static bool IsAlive(Object? value) { try { return value != null && value != (Object)null; } catch { return false; } } } internal static class FishWarehouseNativeGridPlacementPatches { private static IntPtr _gridPointer; private static Vector3 _referenceCenterLocal; private static float _gridCellSize; public static void Apply(Harmony harmony) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_00a3: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Expected O, but got Unknown MethodInfo methodInfo = AccessTools.Method(typeof(Grid), "GetMatchedCoordinate", new Type[1] { typeof(FootprintTile) }, (Type[])null); MethodInfo methodInfo2 = AccessTools.Method(typeof(GridItem), "GetParentTileAtFootprintCoordinate", new Type[1] { typeof(Coordinate) }, (Type[])null); if ((object)methodInfo == null || (object)methodInfo2 == null) { throw new MissingMethodException("Could not resolve the native Grid placement coordinate surfaces."); } harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(FishWarehouseNativeGridPlacementPatches), "MatchedCoordinatePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)methodInfo2, (HarmonyMethod)null, new HarmonyMethod(AccessTools.Method(typeof(FishWarehouseNativeGridPlacementPatches), "ParentTilePostfix", (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } public static void Register(Grid grid, Vector3 referenceCenterLocal, float gridCellSize) { //IL_0050: 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) if (grid == null) { throw new ArgumentNullException("grid"); } if (!float.IsFinite(gridCellSize) || gridCellSize <= 0f) { throw new ArgumentOutOfRangeException("gridCellSize"); } _gridPointer = GetPointer((Il2CppObjectBase)(object)grid); if (_gridPointer == IntPtr.Zero) { throw new InvalidOperationException("The Fish Warehouse Grid did not expose a native object pointer."); } _referenceCenterLocal = referenceCenterLocal; _gridCellSize = gridCellSize; } public static void Unregister(Grid? grid) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) if (grid == null || GetPointer((Il2CppObjectBase)(object)grid) == _gridPointer) { _gridPointer = IntPtr.Zero; _referenceCenterLocal = default(Vector3); _gridCellSize = 0f; } } private static void MatchedCoordinatePostfix(Grid __instance, FootprintTile __0, ref Coordinate __result) { //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_002e: 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_0035: 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_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0095: Expected O, but got Unknown //IL_00a8: Unknown result type (might be due to invalid IL or missing references) try { if (IsActiveGrid(__instance) && __0 != null && ((Component)__0).transform != null) { Vector3 val = ((Component)__instance).transform.InverseTransformPoint(((Component)__0).transform.position); if (!FishWarehouseBuildableFootprintDefinition.TryMapLocalPointToCoordinate(new FishWarehousePoint(val.x, val.y, val.z), new FishWarehousePoint(_referenceCenterLocal.x, _referenceCenterLocal.y, _referenceCenterLocal.z), _gridCellSize, out var coordinate)) { __result = null; return; } Tile tile = __instance.GetTile(new Coordinate(coordinate.X, coordinate.Y)); __result = ((tile == null) ? ((Coordinate)null) : new Coordinate(coordinate.X, coordinate.Y)); } } catch { } } private static void ParentTilePostfix(GridItem __instance, Coordinate __0, ref Tile __result) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0035: Expected O, but got Unknown try { if (__result == null && __instance != null && __0 != null) { Grid ownerGrid = __instance.OwnerGrid; if (IsActiveGrid(ownerGrid)) { __result = ownerGrid.GetTile(new Coordinate(__0.x, __0.y)) ?? null; } } } catch { } } private static bool IsActiveGrid(Grid? grid) { if (grid != null && GetPointer((Il2CppObjectBase)(object)grid) != IntPtr.Zero) { return GetPointer((Il2CppObjectBase)(object)grid) == _gridPointer; } return false; } private static IntPtr GetPointer(Il2CppObjectBase value) { return value.Pointer; } } public sealed class FishWarehouseNativeNavigationBuildLifecycle { private const int DefaultAgentTypeId = 0; private const string LockerTargetName = "employee-locker"; private const string PackagingStationTargetName = "packaging-station"; private readonly FishWarehouseNativeNavigationResourceLedger _resources = new FishWarehouseNativeNavigationResourceLedger(); private IFishWarehouseNativeNavigationAdapter? _adapter; private bool _mutated; public bool IsBuilt { get; private set; } public Exception? LastFailure { get; private set; } public IReadOnlyList LastWarnings { get; private set; } = Array.Empty(); public int? ResolvedEmployeeAgentTypeId { get; private set; } public FishWarehouseNativeNavigationBuildResult TryBuild(IFishWarehouseNativeNavigationAdapter adapter, FishWarehouseNativeNavigationBuildRequest request) { ArgumentNullException.ThrowIfNull(adapter, "adapter"); ArgumentNullException.ThrowIfNull(request, "request"); if (IsBuilt) { return FishWarehouseNativeNavigationBuildResult.Succeeded; } if (_resources.HasPendingTeardown) { LastFailure = new InvalidOperationException("Fish Warehouse native navigation cannot build while prior native cleanup remains pending."); return FishWarehouseNativeNavigationBuildResult.Failed; } FishWarehouseEmployeeAgentTypeResolutionResult fishWarehouseEmployeeAgentTypeResolutionResult; try { IReadOnlyList settings = adapter.GetAgentSettings() ?? Array.Empty(); fishWarehouseEmployeeAgentTypeResolutionResult = FishWarehouseEmployeeAgentTypeResolver.Resolve(request.PreferredEmployeeAgentTypeId, settings, Array.Empty(), Array.Empty()); if (fishWarehouseEmployeeAgentTypeResolutionResult.Status == FishWarehouseEmployeeAgentTypeResolutionStatus.Pending) { IReadOnlyList source = adapter.FindLiveEmployeeAgents(FishWarehouseNativeNavigationEmployeeScope.PropertyAssigned) ?? Array.Empty(); fishWarehouseEmployeeAgentTypeResolutionResult = FishWarehouseEmployeeAgentTypeResolver.Resolve(request.PreferredEmployeeAgentTypeId, settings, source.Select((FishWarehouseNativeNavigationLiveEmployeeAgent employee) => employee.AgentTypeId).ToArray(), Array.Empty()); } if (fishWarehouseEmployeeAgentTypeResolutionResult.Status == FishWarehouseEmployeeAgentTypeResolutionStatus.Pending) { IReadOnlyList source2 = adapter.FindLiveEmployeeAgents(FishWarehouseNativeNavigationEmployeeScope.Global) ?? Array.Empty(); fishWarehouseEmployeeAgentTypeResolutionResult = FishWarehouseEmployeeAgentTypeResolver.Resolve(request.PreferredEmployeeAgentTypeId, settings, Array.Empty(), source2.Select((FishWarehouseNativeNavigationLiveEmployeeAgent employee) => employee.AgentTypeId).ToArray()); } } catch (Exception failure) { return Fail(failure); } if (fishWarehouseEmployeeAgentTypeResolutionResult.Status == FishWarehouseEmployeeAgentTypeResolutionStatus.Pending) { return FishWarehouseNativeNavigationBuildResult.Pending; } if (fishWarehouseEmployeeAgentTypeResolutionResult.Status == FishWarehouseEmployeeAgentTypeResolutionStatus.Failed) { return Fail(new InvalidOperationException(fishWarehouseEmployeeAgentTypeResolutionResult.FailureReason)); } _adapter = adapter; FishWarehouseEmployeeAgentTypeResolution resolution = fishWarehouseEmployeeAgentTypeResolutionResult.Resolution; int agentTypeId = resolution.AgentTypeId; try { ValidateIdlePointCount(request.IdlePoints); int[] array = ((agentTypeId == 0) ? new int[1] : new int[2] { agentTypeId, 0 }); Dictionary dictionary = new Dictionary(); int[] array2 = array; foreach (int num2 in array2) { FishWarehouseNativeNavigationRuntimeGraphProbe value = adapter.ProbeGraph(num2, request.World); if (value.AgentTypeId != num2) { throw new InvalidOperationException($"Fish Warehouse native navigation graph probe returned agent ID {value.AgentTypeId} for requested ID {num2}."); } if (!value.ExteriorSampled) { throw new InvalidOperationException($"Fish Warehouse native navigation could not sample the exterior for agent ID {num2}."); } dictionary.Add(num2, value); } array2 = array; foreach (int num3 in array2) { ApplyGraphDecision(num3, dictionary[num3], request.World); } if (_mutated) { adapter.ClearNativeSampleCache(); } FishWarehouseNativeNavigationAcceptanceResult fishWarehouseNativeNavigationAcceptanceResult = Validate(request, dictionary[agentTypeId], dictionary[0], agentTypeId); LastWarnings = fishWarehouseNativeNavigationAcceptanceResult.Warnings; if (!fishWarehouseNativeNavigationAcceptanceResult.Succeeded) { throw new InvalidOperationException("Fish Warehouse native navigation validation failed: " + string.Join(" | ", fishWarehouseNativeNavigationAcceptanceResult.Failures)); } LastFailure = null; IsBuilt = true; ResolvedEmployeeAgentTypeId = resolution.AgentTypeId; return FishWarehouseNativeNavigationBuildResult.Succeeded; } catch (Exception failure2) { return Fail(failure2); } } public bool Remove() { IsBuilt = false; LastWarnings = Array.Empty(); ResolvedEmployeeAgentTypeId = null; try { RemoveOwnedResources(); _mutated = false; _adapter = null; LastFailure = null; return true; } catch (Exception lastFailure) { LastFailure = lastFailure; return false; } } private void ApplyGraphDecision(int agentTypeId, FishWarehouseNativeNavigationRuntimeGraphProbe probe, FishWarehouseNativeNavigationWorldDescriptor world) { FishWarehouseNativeGraphDecision fishWarehouseNativeGraphDecision = FishWarehouseNativeGraphDecisionDefinition.Decide(new FishWarehouseNativeGraphProbe(probe.ExteriorSampled, probe.InteriorSampled, probe.PathComplete)); if (fishWarehouseNativeGraphDecision == FishWarehouseNativeGraphDecision.Fail) { throw new InvalidOperationException($"Fish Warehouse native navigation graph decision failed for agent ID {agentTypeId}."); } FishWarehouseNativeNavigationGraphRole graphRole = ((agentTypeId != 0) ? FishWarehouseNativeNavigationGraphRole.Employee : FishWarehouseNativeNavigationGraphRole.Default); if (fishWarehouseNativeGraphDecision == FishWarehouseNativeGraphDecision.AddSurfaceAndLink) { int resourceKey = _adapter.AddSyntheticSurface(agentTypeId, world); _resources.RecordData(graphRole, resourceKey); _mutated = true; } if ((uint)(fishWarehouseNativeGraphDecision - 1) <= 1u) { int resourceKey2 = _adapter.AddLink(agentTypeId, world, probe.ExteriorPosition); _resources.RecordLink(graphRole, resourceKey2); _mutated = true; } } private FishWarehouseNativeNavigationAcceptanceResult Validate(FishWarehouseNativeNavigationBuildRequest request, FishWarehouseNativeNavigationRuntimeGraphProbe employeeProbe, FishWarehouseNativeNavigationRuntimeGraphProbe defaultProbe, int employeeAgentTypeId) { List list = new List { ObserveRequired(FishWarehouseNativeNavigationGraphRole.Employee, employeeAgentTypeId, "employee-exterior-to-doorway", employeeProbe.ExteriorPosition, request.World.DoorwayInterior) }; for (int i = 0; i < request.IdlePoints.Count; i++) { list.Add(ObserveRequired(FishWarehouseNativeNavigationGraphRole.Employee, employeeAgentTypeId, FishWarehouseNativeNavigationAcceptanceDefinition.DoorwayToIdleTargetName(i), request.World.DoorwayInterior, request.IdlePoints[i])); } list.Add(ObserveRequired(FishWarehouseNativeNavigationGraphRole.Default, 0, "default-exterior-to-doorway", defaultProbe.ExteriorPosition, request.World.DoorwayInterior)); list.Add(ObserveOptional(employeeAgentTypeId, "employee-locker", request.LockerPoint, request.World.DoorwayInterior)); list.Add(ObserveOptional(employeeAgentTypeId, "packaging-station", request.PackagingStationPoint, request.World.DoorwayInterior)); return FishWarehouseNativeNavigationAcceptanceDefinition.Evaluate(list); } private FishWarehouseNativeNavigationProbe ObserveRequired(FishWarehouseNativeNavigationGraphRole graphRole, int agentTypeId, string targetName, FishWarehouseNativeNavigationVector start, FishWarehouseNativeNavigationVector end) { FishWarehouseNativeNavigationPathObservation fishWarehouseNativeNavigationPathObservation = _adapter.ValidatePath(graphRole, agentTypeId, targetName, start, end); return new FishWarehouseNativeNavigationProbe(graphRole, agentTypeId, targetName, Required: true, TargetPresent: true, fishWarehouseNativeNavigationPathObservation.StartSampled, fishWarehouseNativeNavigationPathObservation.EndSampled, fishWarehouseNativeNavigationPathObservation.PathStatus); } private FishWarehouseNativeNavigationProbe ObserveOptional(int agentTypeId, string targetName, FishWarehouseNativeNavigationVector? target, FishWarehouseNativeNavigationVector start) { if (!target.HasValue) { return new FishWarehouseNativeNavigationProbe(FishWarehouseNativeNavigationGraphRole.Employee, agentTypeId, targetName, Required: false, TargetPresent: false, StartSampled: false, EndSampled: false, FishWarehouseNativeNavigationPathStatus.PathInvalid); } FishWarehouseNativeNavigationPathObservation fishWarehouseNativeNavigationPathObservation = _adapter.ValidatePath(FishWarehouseNativeNavigationGraphRole.Employee, agentTypeId, targetName, start, target.Value); return new FishWarehouseNativeNavigationProbe(FishWarehouseNativeNavigationGraphRole.Employee, agentTypeId, targetName, Required: false, TargetPresent: true, fishWarehouseNativeNavigationPathObservation.StartSampled, fishWarehouseNativeNavigationPathObservation.EndSampled, fishWarehouseNativeNavigationPathObservation.PathStatus); } private FishWarehouseNativeNavigationBuildResult Fail(Exception failure) { IsBuilt = false; _mutated = false; LastWarnings = Array.Empty(); ResolvedEmployeeAgentTypeId = null; LastFailure = failure; try { RemoveOwnedResources(); } catch (Exception ex) { LastFailure = new AggregateException(failure, ex); } return FishWarehouseNativeNavigationBuildResult.Failed; } private static void ValidateIdlePointCount(IReadOnlyList? idlePoints) { int num = 10; if (num != FishWarehouseEmployeeInfrastructureDefinition.IdlePoints.Count) { throw new InvalidOperationException($"Fish Warehouse native navigation rejected capacity-idle-point-count-mismatch: capacity={num} definitionIdlePoints={FishWarehouseEmployeeInfrastructureDefinition.IdlePoints.Count}."); } if (idlePoints == null) { throw new InvalidOperationException($"Fish Warehouse native navigation rejected missing-idle-points: expected={num} actual=0."); } if (idlePoints.Count == num) { return; } string value = ((idlePoints.Count < num) ? "too-few-idle-points" : "too-many-idle-points"); throw new InvalidOperationException($"Fish Warehouse native navigation rejected {value}: expected={num} actual={idlePoints.Count}."); } private void RemoveOwnedResources() { if (_adapter == null) { return; } foreach (FishWarehouseNativeNavigationTeardownOperation item in _resources.PendingTeardown()) { _adapter.Remove(item); _resources.Acknowledge(item); } } } public sealed class FishWarehouseNativeNavigationHost { private readonly Func?, IFishWarehouseNativeNavigationAdapter> _adapterFactory; private readonly FishWarehouseNativeNavigationBuildLifecycle _lifecycle = new FishWarehouseNativeNavigationBuildLifecycle(); public bool IsBuilt => _lifecycle.IsBuilt; public Exception? LastFailure => _lifecycle.LastFailure; public int? ResolvedEmployeeAgentTypeId => _lifecycle.ResolvedEmployeeAgentTypeId; public FishWarehouseNativeNavigationHost(Func?, IFishWarehouseNativeNavigationAdapter>? adapterFactory = null) { _adapterFactory = adapterFactory ?? ((Func, IFishWarehouseNativeNavigationAdapter>)((Transform propertyRoot, Action? log) => new UnityNativeNavigationAdapter(propertyRoot))); } public FishWarehouseNativeNavigationBuildResult TryBuild(IFishWarehouseNativeNavigationAdapter adapter, FishWarehouseNativeNavigationBuildRequest request, Action? log = null) { return _lifecycle.TryBuild(adapter, request); } public FishWarehouseNativeNavigationBuildResult TryBuild(Transform anchor, Transform propertyRoot, FishWarehouseGarageOpeningProjection projection, IEnumerable idlePoints, Transform? restoredLocker, PackagingStation? representativePackagingStation, int? preferredEmployeeAgentTypeId = null, Action? log = null) { //IL_016f: Unknown result type (might be due to invalid IL or missing references) //IL_019a: Unknown result type (might be due to invalid IL or missing references) if (!IsAlive((Object?)(object)anchor) || !IsAlive((Object?)(object)propertyRoot) || (object)projection == null || idlePoints == null || !FishWarehouseTransformSignatureDefinition.IsCoincident(ToTransformSignature(anchor), ToTransformSignature(propertyRoot))) { return FailClosed("Fish Warehouse native navigation build rejected: required anchor, Property, or projection was unavailable.", log); } Transform[] array = idlePoints.ToArray(); int num = 10; if (array.Length != num) { string value = ((array.Length < num) ? "too-few-idle-points" : "too-many-idle-points"); return FailClosed($"Fish Warehouse native navigation build rejected {value}: expected={num} actual={array.Length}.", log); } if (array.Any((Transform point) => !IsAlive((Object?)(object)point))) { return FailClosed($"Fish Warehouse native navigation build rejected malformed-idle-points: expected={num} valid live points.", log); } try { FishWarehouseNativeNavigationBuildRequest request = new FishWarehouseNativeNavigationBuildRequest(ToWorldDescriptor(FishWarehouseNativeNavigationDefinition.Create(projection), propertyRoot), array.Select((Transform point) => ToModelVector(point.position)).ToArray(), IsAlive((Object?)(object)restoredLocker) ? new FishWarehouseNativeNavigationVector?(ToModelVector(restoredLocker.position)) : ((FishWarehouseNativeNavigationVector?)null), IsAlive((Object?)(object)representativePackagingStation) ? new FishWarehouseNativeNavigationVector?(ToModelVector(((Component)representativePackagingStation).transform.position)) : ((FishWarehouseNativeNavigationVector?)null), preferredEmployeeAgentTypeId); FishWarehouseNativeNavigationBuildResult fishWarehouseNativeNavigationBuildResult = TryBuild(_adapterFactory(propertyRoot, log), request, log); foreach (string lastWarning in _lifecycle.LastWarnings) { Log(log, "Fish Warehouse native navigation acceptance warning: " + lastWarning + "."); } if (fishWarehouseNativeNavigationBuildResult == FishWarehouseNativeNavigationBuildResult.Failed) { Log(log, "Fish Warehouse native navigation build failed safely: " + _lifecycle.LastFailure?.Message); } return fishWarehouseNativeNavigationBuildResult; } catch (Exception ex) { Log(log, "Fish Warehouse native navigation build failed safely: " + ex.Message); _lifecycle.Remove(); return FishWarehouseNativeNavigationBuildResult.Failed; } } public bool Remove() { return _lifecycle.Remove(); } private FishWarehouseNativeNavigationBuildResult FailClosed(string message, Action? log) { Log(log, message); if (!_lifecycle.Remove()) { Log(log, "Fish Warehouse native navigation cleanup remains pending: " + _lifecycle.LastFailure?.Message); } return FishWarehouseNativeNavigationBuildResult.Failed; } private static void Log(Action? log, string message) { try { log?.Invoke(message); } catch { } } private static FishWarehouseNativeNavigationWorldDescriptor ToWorldDescriptor(FishWarehouseNativeNavigationPlan plan, Transform propertyRoot) { //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) //IL_0051: 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_0076: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_0097: Unknown result type (might be due to invalid IL or missing references) return new FishWarehouseNativeNavigationWorldDescriptor(plan.Surface with { Center = ToModelVector(propertyRoot.TransformPoint(ToUnity(plan.Surface.Center))) }, ToWorldBounds(plan.BakeBounds, propertyRoot), plan.Link with { Start = ToModelVector(propertyRoot.TransformPoint(ToUnity(plan.Link.Start))), NominalExteriorQueryPoint = ToModelVector(propertyRoot.TransformPoint(ToUnity(plan.Link.NominalExteriorQueryPoint))) }, ToModelVector(propertyRoot.TransformPoint(ToUnity(plan.DoorwayInterior))), plan.SampleRadius); } private static FishWarehouseNativeNavigationBox ToWorldBounds(FishWarehouseNativeNavigationBox localBounds, Transform propertyRoot) { //IL_0007: 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_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_0027: 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_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_0033: Unknown result type (might be due to invalid IL or missing references) //IL_00e7: 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_0095: Unknown result type (might be due to invalid IL or missing references) //IL_0096: 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_00aa: 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) Vector3 val = ToUnity(localBounds.Size) * 0.5f; Vector3 val2 = ToUnity(localBounds.Center); Bounds val3 = default(Bounds); ((Bounds)(ref val3))..ctor(propertyRoot.TransformPoint(val2 - val), Vector3.zero); int[] array = new int[2] { -1, 1 }; foreach (int num in array) { int[] array2 = new int[2] { -1, 1 }; foreach (int num2 in array2) { int[] array3 = new int[2] { -1, 1 }; foreach (int num3 in array3) { ((Bounds)(ref val3)).Encapsulate(propertyRoot.TransformPoint(val2 + Vector3.Scale(val, new Vector3((float)num, (float)num2, (float)num3)))); } } } return new FishWarehouseNativeNavigationBox(ToModelVector(((Bounds)(ref val3)).center), ToModelVector(((Bounds)(ref val3)).size)); } private static FishWarehouseTransformSignature ToTransformSignature(Transform transform) { //IL_0001: 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_0017: 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_0032: 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_004d: 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_0063: Unknown result type (might be due to invalid IL or missing references) return new FishWarehouseTransformSignature(new FishWarehouseTransformVector(transform.position.x, transform.position.y, transform.position.z), new FishWarehouseTransformVector(transform.eulerAngles.x, transform.eulerAngles.y, transform.eulerAngles.z), new FishWarehouseTransformVector(transform.lossyScale.x, transform.lossyScale.y, transform.lossyScale.z)); } private static FishWarehouseNativeNavigationVector ToModelVector(Vector3 value) { //IL_0000: 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) return new FishWarehouseNativeNavigationVector(value.x, value.y, value.z); } private static Vector3 ToUnity(FishWarehouseNativeNavigationVector value) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(value.X, value.Y, value.Z); } private static bool IsAlive(Object? value) { try { return value != null && value != (Object)null; } catch { return false; } } } public sealed class FishWarehouseNativePropertyReplayAdapter : IFishWarehouseNativePropertyReplayAdapter { private readonly PropertyData _propertyData; private readonly Property _property; private readonly Action _log; private readonly Dictionary _loaders = new Dictionary(StringComparer.Ordinal); public FishWarehouseNativePropertyReplayAdapter(PropertyData propertyData, Property property, Action? log = null) { _propertyData = propertyData ?? throw new ArgumentNullException("propertyData"); _property = property ?? throw new ArgumentNullException("property"); _log = log ?? ((Action)delegate { }); } public bool IsGuidRegistered(string guid) { if (!TryInvokeGuidInterop(guid, (Func)GUIDManager.IsGUIDAlreadyRegistered, out bool result)) { _log("Fish Warehouse native replay rejected malformed GUID before interop: " + guid); return false; } return result; } public bool TryGetObjectLoaderLoadOrder(string dataType, out int loadOrder) { loadOrder = 0; if (string.IsNullOrWhiteSpace(dataType)) { return false; } if (!_loaders.TryGetValue(dataType, out BuildableItemLoader value)) { LoadManager instance = Singleton.Instance; value = ((instance != null) ? instance.GetObjectLoader(dataType) : null); if (value == null) { return false; } _loaders.Add(dataType, value); } loadOrder = value.LoadOrder; return true; } public void LoadObject(int payloadIndex) { if (payloadIndex < 0 || payloadIndex >= ((Il2CppArrayBase)(object)_propertyData.Objects).Count) { throw new ArgumentOutOfRangeException("payloadIndex"); } DynamicSaveData val = ((Il2CppArrayBase)(object)_propertyData.Objects)[payloadIndex]; if (!_loaders.TryGetValue(((SaveData)val).DataType, out BuildableItemLoader value)) { throw new InvalidOperationException("No registry-owned loader was resolved for " + ((SaveData)val).DataType + "."); } value.Load(val); } public FishWarehouseRestoredObjectObservation ObserveObject(string guid) { if (!TryInvokeGuidInterop(guid, (Guid nativeGuid) => GUIDManager.GetObject(nativeGuid), out var result) || result == null) { return new FishWarehouseRestoredObjectObservation(IsResolved: false, null, IsEmployeeHome: false); } Property parentProperty = result.ParentProperty; string parentPropertyCode = ((parentProperty != null) ? parentProperty.PropertyCode : null); bool flag = ((Component)result).GetComponent() != null; return new FishWarehouseRestoredObjectObservation(IsResolved: true, parentPropertyCode, flag && (Object)(object)result.ParentProperty == (Object)(object)_property); } public static bool TryCreateDescriptors(PropertyData propertyData, out IReadOnlyList descriptors, out string? failureReason) { descriptors = Array.Empty(); failureReason = null; if (propertyData == null) { failureReason = "Fish Warehouse replay descriptors could not be created because captured PropertyData was unavailable."; return false; } List list = new List(); for (int i = 0; i < ((Il2CppArrayBase)(object)propertyData.Objects).Count; i++) { DynamicSaveData val = ((Il2CppArrayBase)(object)propertyData.Objects)[i]; try { using JsonDocument jsonDocument = JsonDocument.Parse(val.BaseData); JsonElement rootElement = jsonDocument.RootElement; string text = rootElement.GetProperty("GUID").GetString(); if (string.IsNullOrWhiteSpace(text)) { throw new JsonException("GUID was unavailable."); } string a = ReadItemId(rootElement); list.Add(new FishWarehouseSavedObjectDescriptor(i, ((SaveData)val).DataType, text, rootElement.GetProperty("LoadOrder").GetInt32(), string.Equals(((SaveData)val).DataType, "ProceduralGridItemData", StringComparison.Ordinal), string.Equals(a, "locker", StringComparison.OrdinalIgnoreCase))); } catch (Exception ex) when (((ex is JsonException || ex is InvalidOperationException || ex is FormatException || ex is KeyNotFoundException) ? 1 : 0) != 0) { failureReason = $"Fish Warehouse replay descriptor failed at object index {i}: {ex.Message}"; return false; } } descriptors = list.AsReadOnly(); return true; } internal static bool TryInvokeGuidInterop(string savedGuid, Func interopCall, out T result) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (!Guid.TryParse(savedGuid, out var result2)) { result = default(T); return false; } result = interopCall(new Guid(result2.ToString())); return true; } private static string ReadItemId(JsonElement baseData) { if (!baseData.TryGetProperty("ItemString", out var value)) { return string.Empty; } if (value.ValueKind == JsonValueKind.Object) { if (!value.TryGetProperty("ID", out var value2)) { return string.Empty; } return value2.GetString() ?? string.Empty; } if (value.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(value.GetString())) { return string.Empty; } using JsonDocument jsonDocument = JsonDocument.Parse(value.GetString()); JsonElement value3; return jsonDocument.RootElement.TryGetProperty("ID", out value3) ? (value3.GetString() ?? string.Empty) : string.Empty; } } internal static class FishWarehouseNativePropertyWriteGuardPatch { private static Func _isAuthoritativeHost = () => false; private static Func _isDegraded = () => false; public static void Apply(Harmony harmony, Func isAuthoritativeHost, Func isDegraded) { //IL_008b: Unknown result type (might be due to invalid IL or missing references) //IL_0099: Expected O, but got Unknown _isAuthoritativeHost = isAuthoritativeHost; _isDegraded = isDegraded; MethodInfo methodInfo = AccessTools.Method(typeof(Property), "WriteData", new Type[1] { typeof(string) }, (Type[])null) ?? throw new MissingMethodException(typeof(Property).FullName, "WriteData"); MethodInfo methodInfo2 = AccessTools.Method(typeof(FishWarehouseNativePropertyWriteGuardPatch), "Prefix", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(FishWarehouseNativePropertyWriteGuardPatch).FullName, "Prefix"); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool Prefix(Property __instance, ref List __result) { return ApplyWriteGuardDecision(EvaluateWriteGuard(__instance), ref __result); } internal static FishWarehousePropertyWriteGuardDecision EvaluateWriteGuard(Property property) { if (property == null) { return FishWarehousePropertyWriteGuardDecision.RunOriginal; } try { return EvaluateWriteGuard(_isAuthoritativeHost(), property.PropertyCode, _isDegraded()); } catch { return FishWarehousePropertyWriteGuardDecision.RunOriginal; } } internal static FishWarehousePropertyWriteGuardDecision EvaluateWriteGuard(bool isAuthoritativeHost, string? propertyCode, bool isDegraded) { if (!(isAuthoritativeHost && isDegraded) || !string.Equals(propertyCode, "oc_fishwarehouse", StringComparison.Ordinal)) { return FishWarehousePropertyWriteGuardDecision.RunOriginal; } return FishWarehousePropertyWriteGuardDecision.SuppressWithEmptyResult; } internal static bool ApplyWriteGuardDecision(FishWarehousePropertyWriteGuardDecision decision, ref List __result) { if (decision == FishWarehousePropertyWriteGuardDecision.RunOriginal) { return true; } __result = new List(); return false; } public static void Reset() { _isAuthoritativeHost = () => false; _isDegraded = () => false; } } public enum FishWarehouseEmployeeHomeAdoptionResult { NoneFound, Adopted, MultipleFound, Failed } public enum FishWarehouseReplayOperationState { Pending, Succeeded, Failed } public sealed record FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState State, int Count, string? FailureReason, IReadOnlyList? InventoryRestoredEmployeeGuids = null) { public static FishWarehouseReplayOperationResult Pending() { return new FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState.Pending, 0, null); } public static FishWarehouseReplayOperationResult Succeeded(int count) { return new FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState.Succeeded, count, null); } public static FishWarehouseReplayOperationResult Failed(string reason) { return new FishWarehouseReplayOperationResult(FishWarehouseReplayOperationState.Failed, 0, reason); } } public interface IFishWarehousePersistenceReplayOperations { bool IsRuntimeReady { get; } bool IsNavigationReady { get; } void ConfigureReplay(bool objectReplayRequired, int? employeeAgentTypeId); void RequestOwnedRestore(); FishWarehouseReplayOperationResult ReplayObjects(); FishWarehouseReplayOperationResult MarkObjectsReplayed(); FishWarehouseReplayOperationResult ReplayEmployees(); FishWarehouseDeliveryRestoreFlushResult ReleaseDeliveries(); } public sealed class FishWarehousePersistenceReplayService { private readonly FishWarehouseReplayLifecycle _lifecycle; private readonly Func _activeSaveFolder; private readonly Func _recoverCapture; private readonly Func _loadState; private readonly Func _currentState; private readonly IFishWarehousePersistenceReplayOperations _operations; private readonly Func _persistCheckpoint; private readonly Action? _dropTypedReferences; private readonly Action? _resetDeliveries; private readonly Action? _teardownRuntime; private readonly Func _saveFolderKey; private readonly Action _log; private bool _loadCompleteHandled; private bool _generationArmed; private bool _keepDeliveryFlushAliveAfterFailure; private FishWarehouseReplayPhase _phase; private string? _failureReason; public FishWarehouseReplayPhase Phase => _phase; public string? FailureReason => _failureReason; public bool RequiresSnapshotProtection { get { if (!_lifecycle.RequiresSnapshotProtection && (_phase < FishWarehouseReplayPhase.Captured || _phase > FishWarehouseReplayPhase.DeliveriesReleased)) { return _phase == FishWarehouseReplayPhase.Failed; } return true; } } public static void RunIfAuthoritativeHost(bool isAuthoritativeHost, Action callback) { if (isAuthoritativeHost) { callback(); } } public FishWarehousePersistenceReplayService(FishWarehouseReplayLifecycle lifecycle, Func activeSaveFolder, Func recoverCapture, Func loadState, Func currentState, IFishWarehousePersistenceReplayOperations operations, Func persistCheckpoint, Action? dropTypedReferences = null, Action? resetDeliveries = null, Action? teardownRuntime = null, Func? saveFolderKey = null, Action? log = null) { _lifecycle = lifecycle ?? throw new ArgumentNullException("lifecycle"); _activeSaveFolder = activeSaveFolder ?? throw new ArgumentNullException("activeSaveFolder"); _recoverCapture = recoverCapture ?? throw new ArgumentNullException("recoverCapture"); _loadState = loadState ?? throw new ArgumentNullException("loadState"); _currentState = currentState ?? throw new ArgumentNullException("currentState"); _operations = operations ?? throw new ArgumentNullException("operations"); _persistCheckpoint = persistCheckpoint ?? throw new ArgumentNullException("persistCheckpoint"); _dropTypedReferences = dropTypedReferences; _resetDeliveries = resetDeliveries; _teardownRuntime = teardownRuntime; _saveFolderKey = saveFolderKey ?? new Func(FishWarehouseSaveFolderKey.Create); _log = log ?? ((Action)delegate { }); } public void HandleLoadComplete() { if (_loadCompleteHandled) { return; } _loadCompleteHandled = true; string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { FailCurrentPhase("Fish Warehouse replay could not start because the active save folder was unavailable."); return; } FishWarehousePropertyCaptureResult fishWarehousePropertyCaptureResult; try { fishWarehousePropertyCaptureResult = _recoverCapture(text); } catch (Exception ex) { FailCurrentPhase("capture recovery threw: " + ex.Message); return; } FishWarehousePropertyCaptureState state = fishWarehousePropertyCaptureResult.State; if ((uint)(state - 2) <= 1u) { FailCurrentPhase(fishWarehousePropertyCaptureResult.FailureReason ?? "Fish Warehouse capture recovery remained degraded."); return; } bool flag; try { flag = _loadState(); } catch (Exception ex2) { FailCurrentPhase("save state load threw: " + ex2.Message); return; } if (!flag) { return; } FishWarehouseSaveState fishWarehouseSaveState = _currentState(); if ((object)fishWarehouseSaveState == null) { return; } try { if ((object)fishWarehouseSaveState.Replay == null || fishWarehouseSaveState.Replay.Phase == FishWarehouseReplayPhase.Complete) { _operations.ConfigureReplay(objectReplayRequired: false, fishWarehouseSaveState.EmployeeAgentTypeId); _operations.RequestOwnedRestore(); } else if (fishWarehouseSaveState.Replay.Phase == FishWarehouseReplayPhase.Failed) { FishWarehouseReplayCheckpoint currentCheckpoint = _lifecycle.CurrentCheckpoint; if ((object)currentCheckpoint == null || currentCheckpoint.Phase != FishWarehouseReplayPhase.Failed || fishWarehousePropertyCaptureResult.State != FishWarehousePropertyCaptureState.Captured) { _phase = FishWarehouseReplayPhase.Failed; _failureReason = fishWarehouseSaveState.Replay.FailureReason; return; } FishWarehouseReplayCheckpoint recoveredCapturedCheckpoint = currentCheckpoint with { Phase = FishWarehouseReplayPhase.Captured, FailurePhase = null, FailureReason = null }; if (!_lifecycle.TryRecoverCaptureFailure(recoveredCapturedCheckpoint, out string failureReason)) { _phase = FishWarehouseReplayPhase.Failed; _failureReason = failureReason ?? fishWarehouseSaveState.Replay.FailureReason; return; } _log("Fish Warehouse replay re-armed after failed checkpoint: " + (currentCheckpoint.FailureReason ?? fishWarehouseSaveState.Replay.FailureReason ?? "unknown failure") + "."); if (!_persistCheckpoint(_lifecycle.CurrentCheckpoint)) { _phase = FishWarehouseReplayPhase.Captured; FailCurrentPhase("re-armed replay checkpoint persistence failed."); return; } _phase = FishWarehouseReplayPhase.Captured; _generationArmed = true; _operations.ConfigureReplay(objectReplayRequired: true, fishWarehouseSaveState.EmployeeAgentTypeId); _operations.RequestOwnedRestore(); } else if (fishWarehousePropertyCaptureResult.State != FishWarehousePropertyCaptureState.Captured || !IsActiveSaveFolder(fishWarehouseSaveState.Replay.SaveFolderKey, text)) { FailCurrentPhase("Fish Warehouse replay checkpoint did not match the captured active save folder."); } else if ((object)_lifecycle.CurrentCheckpoint == null || _lifecycle.CurrentCheckpoint.GenerationId != fishWarehouseSaveState.Replay.GenerationId) { FailCurrentPhase("Fish Warehouse replay generation was not active for the captured save."); } else { _phase = fishWarehouseSaveState.Replay.Phase; _generationArmed = true; _operations.ConfigureReplay(objectReplayRequired: true, fishWarehouseSaveState.EmployeeAgentTypeId); _operations.RequestOwnedRestore(); } } catch (Exception ex3) { FailCurrentPhase("load-complete replay setup threw: " + ex3.Message); } } public void Update() { if (!_loadCompleteHandled) { return; } if (!_generationArmed) { if (_phase == FishWarehouseReplayPhase.Inactive) { try { _operations.ReleaseDeliveries(); } catch (Exception ex) { _log("Fish Warehouse fresh-load delivery flush failed safely: " + ex.Message); } } } else if (_phase == FishWarehouseReplayPhase.Failed) { if (_keepDeliveryFlushAliveAfterFailure) { FlushDeliveriesAfterFailure(); } } else { if (_phase == FishWarehouseReplayPhase.Complete) { return; } string text = _activeSaveFolder(); if (!string.IsNullOrWhiteSpace(text) && (object)_lifecycle.CurrentCheckpoint != null && IsActiveSaveFolder(_lifecycle.CurrentCheckpoint.SaveFolderKey, text)) { try { switch (_phase) { case FishWarehouseReplayPhase.Captured: if (_operations.IsRuntimeReady) { TryAdvance(FishWarehouseReplayPhase.RuntimeReady); } break; case FishWarehouseReplayPhase.RuntimeReady: RunObjectsPhase(); break; case FishWarehouseReplayPhase.ObjectsReplayed: if (_operations.IsNavigationReady) { TryAdvance(FishWarehouseReplayPhase.NavigationReady); } break; case FishWarehouseReplayPhase.NavigationReady: RunDeliveriesPhase(); break; case FishWarehouseReplayPhase.EmployeesReplayed: RunDeliveriesPhase(); break; case FishWarehouseReplayPhase.DeliveriesReleased: RunEmployeesPhase(); break; } return; } catch (Exception ex2) { FailCurrentPhase("replay update threw: " + ex2.Message); return; } } FailCurrentPhase("Fish Warehouse replay cannot continue under a different active save folder."); } } public void PrepareForLoad() { _loadCompleteHandled = false; _generationArmed = false; _phase = FishWarehouseReplayPhase.Inactive; _failureReason = null; _dropTypedReferences?.Invoke(); _lifecycle.ResetRuntimeReferences(); if ((object)_lifecycle.CurrentCheckpoint == null) { FishWarehouseSaveState fishWarehouseSaveState = _currentState(); if ((object)fishWarehouseSaveState?.Replay != null) { _lifecycle.Begin(fishWarehouseSaveState.Replay, out string _); } } _resetDeliveries?.Invoke(); _teardownRuntime?.Invoke(); } private void RunObjectsPhase() { try { FishWarehouseReplayOperationResult fishWarehouseReplayOperationResult = _operations.ReplayObjects(); if (fishWarehouseReplayOperationResult.State == FishWarehouseReplayOperationState.Pending) { return; } if (fishWarehouseReplayOperationResult.State == FishWarehouseReplayOperationState.Failed) { FailCurrentPhase(fishWarehouseReplayOperationResult.FailureReason ?? "Fish Warehouse object replay failed."); return; } FishWarehouseReplayOperationResult fishWarehouseReplayOperationResult2 = _operations.MarkObjectsReplayed(); if (fishWarehouseReplayOperationResult2.State == FishWarehouseReplayOperationState.Failed) { FailCurrentPhase(fishWarehouseReplayOperationResult2.FailureReason ?? "Fish Warehouse object replay gate could not be marked complete."); } else if (fishWarehouseReplayOperationResult2.State != FishWarehouseReplayOperationState.Pending) { TryAdvance(FishWarehouseReplayPhase.ObjectsReplayed); } } catch (Exception ex) { FailCurrentPhase("object replay threw: " + ex.Message); } } private void RunEmployeesPhase() { try { if (!_operations.IsNavigationReady) { return; } FishWarehouseReplayOperationResult fishWarehouseReplayOperationResult = _operations.ReplayEmployees(); if (TryPersistInventoryRestorationMarkers(fishWarehouseReplayOperationResult.InventoryRestoredEmployeeGuids) && fishWarehouseReplayOperationResult.State != FishWarehouseReplayOperationState.Pending) { if (fishWarehouseReplayOperationResult.State == FishWarehouseReplayOperationState.Failed) { FailCurrentPhase(fishWarehouseReplayOperationResult.FailureReason ?? "Fish Warehouse employee replay failed."); } else { TryAdvance((_phase == FishWarehouseReplayPhase.DeliveriesReleased) ? FishWarehouseReplayPhase.Complete : FishWarehouseReplayPhase.EmployeesReplayed); } } } catch (Exception ex) { FailCurrentPhase("employee replay threw: " + ex.Message); } } private void RunDeliveriesPhase() { try { FishWarehouseDeliveryRestoreFlushResult fishWarehouseDeliveryRestoreFlushResult = _operations.ReleaseDeliveries(); if (fishWarehouseDeliveryRestoreFlushResult.Failure != null) { FailCurrentPhase(fishWarehouseDeliveryRestoreFlushResult.Failure); } else if (fishWarehouseDeliveryRestoreFlushResult.PendingCount == 0) { TryAdvance(FishWarehouseReplayPhase.DeliveriesReleased); } } catch (Exception ex) { FailCurrentPhase("delivery replay threw: " + ex.Message); } } private void TryAdvance(FishWarehouseReplayPhase nextPhase) { if (!_lifecycle.TryAdvance(nextPhase, out string failureReason)) { FailCurrentPhase(failureReason ?? $"Fish Warehouse replay could not advance to {nextPhase}."); } else if (!_persistCheckpoint(_lifecycle.CurrentCheckpoint)) { FailCurrentPhase("replay checkpoint persistence failed."); } else { _phase = nextPhase; } } private void FailCurrentPhase(string reason) { _failureReason = reason; FishWarehouseReplayPhase fishWarehouseReplayPhase = ((_phase == FishWarehouseReplayPhase.Inactive) ? FishWarehouseReplayPhase.Captured : _phase); _lifecycle.Fail(fishWarehouseReplayPhase, reason); if ((object)_lifecycle.CurrentCheckpoint != null) { try { _persistCheckpoint(_lifecycle.CurrentCheckpoint); } catch (Exception ex) { _log("Fish Warehouse failed replay checkpoint persistence threw: " + ex.Message); } } _phase = FishWarehouseReplayPhase.Failed; _keepDeliveryFlushAliveAfterFailure = fishWarehouseReplayPhase == FishWarehouseReplayPhase.DeliveriesReleased; } private bool TryPersistInventoryRestorationMarkers(IReadOnlyList? employeeGuids) { if (employeeGuids == null || employeeGuids.Count == 0) { return true; } HashSet existing = (_lifecycle.CurrentCheckpoint?.InventoryRestoredEmployeeGuids ?? Array.Empty()).Select(CanonicalGuid).ToHashSet(StringComparer.Ordinal); string[] array = (from guid in employeeGuids.Select(CanonicalGuid) where !existing.Contains(guid) select guid).Distinct(StringComparer.Ordinal).ToArray(); if (array.Length == 0) { return true; } if (!_lifecycle.RecordInventoryRestoredEmployeeGuids((IReadOnlyCollection)(object)array, out string failureReason)) { FailCurrentPhase(failureReason ?? "inventory restoration marker could not be recorded."); return false; } if (!_persistCheckpoint(_lifecycle.CurrentCheckpoint)) { FailCurrentPhase("inventory restoration marker persistence failed."); return false; } return true; } private static string CanonicalGuid(string value) { if (!Guid.TryParse(value, out var result)) { return value; } return result.ToString("D"); } private void FlushDeliveriesAfterFailure() { try { FishWarehouseDeliveryRestoreFlushResult fishWarehouseDeliveryRestoreFlushResult = _operations.ReleaseDeliveries(); if (fishWarehouseDeliveryRestoreFlushResult.Failure != null) { _log("Fish Warehouse retained delivery flush failed after replay failure: " + fishWarehouseDeliveryRestoreFlushResult.Failure); } } catch (Exception ex) { _log("Fish Warehouse retained delivery flush threw after replay failure: " + ex.Message); } } private bool IsActiveSaveFolder(string expectedKey, string activeSaveFolder) { try { return string.Equals(_saveFolderKey(activeSaveFolder), expectedKey, StringComparison.Ordinal); } catch (Exception ex) { _failureReason = "active save folder key could not be validated: " + ex.Message; return false; } } } public sealed class FishWarehousePropertyBoundsHost { private Property? _property; private GameObject? _root; private BoxCollider? _collider; private GameObject? _previousBoundingBox; private Il2CppReferenceArray? _previousColliders; public bool IsReady { get { if (_property != null && _root != null) { return _collider != null; } return false; } } public bool TryAttach(Property property, Action? log = null) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Expected O, but got Unknown //IL_0043: 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_0063: 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_008f: 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_0148: 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) if (IsReady) { return true; } if (property == null) { return false; } GameObject val = null; try { val = new GameObject("OC_FishWarehouse_PropertyBounds"); val.transform.SetParent(((Component)property).transform, false); val.transform.localPosition = new Vector3(-1.9499998f, 2.9f, 0f); val.transform.localRotation = Quaternion.identity; val.transform.localScale = Vector3.one; BoxCollider val2 = val.AddComponent(); val2.center = Vector3.zero; val2.size = new Vector3(27.1f, 6.8f, 13.8f); ((Collider)val2).isTrigger = true; _previousBoundingBox = property.BoundingBox; _previousColliders = property.propertyBoundsColliders; Il2CppReferenceArray val3 = new Il2CppReferenceArray(1L); ((Il2CppArrayBase)(object)val3)[0] = val2; property.BoundingBox = val; property.propertyBoundsColliders = val3; _property = property; _root = val; _collider = val2; val = null; if (!EnsureActive() || (Object)(object)property.BoundingBox != (Object)(object)_root || property.propertyBoundsColliders == null || ((Il2CppArrayBase)(object)property.propertyBoundsColliders).Length != 1) { throw new InvalidOperationException("Property did not retain the authored bounds references."); } Vector3 val4 = ((Component)property).transform.TransformPoint(new Vector3(-10.96f, 0f, 6.15f)); if (!property.DoBoundsContainPoint(val4)) { throw new InvalidOperationException("Native Property point containment rejected the interior entry point."); } return true; } catch (Exception ex) { if (_property != null) { _property.BoundingBox = _previousBoundingBox; _property.propertyBoundsColliders = _previousColliders; } if (val != null) { Object.DestroyImmediate((Object)(object)val); } else if (_root != null) { Object.DestroyImmediate((Object)(object)_root); } _property = null; _root = null; _collider = null; _previousBoundingBox = null; _previousColliders = null; log?.Invoke("Fish Warehouse property bounds failed safely: " + ex.Message); return false; } } public bool EnsureActive() { if (_root == null || _collider == null) { return false; } _root.SetActive(true); ((Collider)_collider).enabled = true; return FishWarehousePropertyBoundsDefinition.IsStagedAttachmentUsable(_root.activeSelf, ((Collider)_collider).enabled); } public void Dispose() { if (_property != null) { if ((Object)(object)_property.BoundingBox == (Object)(object)_root) { _property.BoundingBox = _previousBoundingBox; } if (_property.propertyBoundsColliders != null && ((Il2CppArrayBase)(object)_property.propertyBoundsColliders).Length == 1 && (Object)(object)((Il2CppArrayBase)(object)_property.propertyBoundsColliders)[0] == (Object)(object)_collider) { _property.propertyBoundsColliders = _previousColliders; } } if (_root != null) { Object.DestroyImmediate((Object)(object)_root); } _property = null; _root = null; _collider = null; _previousBoundingBox = null; _previousColliders = null; } } public enum FishWarehousePropertyCaptureState { Inactive, Captured, RecoveryRequired, Degraded } public sealed record FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState State, string? FailureReason = null); internal enum FishWarehousePropertyWriteGuardDecision { RunOriginal, SuppressWithEmptyResult } internal sealed record FishWarehousePropertyCaptureAttempt(bool ByteProtectionSucceeded, bool InspectionSucceeded, bool TypedPayloadRetentionSucceeded, bool CheckpointPersistenceSucceeded, string? FailureReason) { public bool AllSucceeded { get { if (ByteProtectionSucceeded && InspectionSucceeded && TypedPayloadRetentionSucceeded) { return CheckpointPersistenceSucceeded; } return false; } } public FishWarehouseCaptureRecoveryPrerequisites ToRecoveryPrerequisites() { return new FishWarehouseCaptureRecoveryPrerequisites(ByteProtectionSucceeded, InspectionSucceeded, TypedPayloadRetentionSucceeded, CheckpointPersistenceSucceeded); } } internal sealed record FishWarehousePropertyCapturePolicyDecision(bool RunOriginal, FishWarehousePropertyCaptureResult Result) { public bool PermitsReplay => FishWarehousePropertyCapturePolicy.PermitsReplay(Result); } internal static class FishWarehousePropertyCapturePolicy { public static FishWarehousePropertyCapturePolicyDecision EvaluateLoad(bool isAuthoritativeHost, string? propertyCode, string? activeSaveFolder, FishWarehousePropertyCaptureAttempt attempt) { if (ShouldPassThrough(isAuthoritativeHost, propertyCode, activeSaveFolder)) { return new FishWarehousePropertyCapturePolicyDecision(RunOriginal: true, new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Inactive)); } return new FishWarehousePropertyCapturePolicyDecision(RunOriginal: false, EvaluateInitialCapture(attempt)); } public static bool ShouldPassThrough(bool isAuthoritativeHost, string? propertyCode, string? activeSaveFolder) { if (isAuthoritativeHost && string.Equals(propertyCode, "oc_fishwarehouse", StringComparison.Ordinal)) { return string.IsNullOrWhiteSpace(activeSaveFolder); } return true; } public static FishWarehousePropertyCaptureResult EvaluateInitialCapture(FishWarehousePropertyCaptureAttempt attempt) { if (!attempt.AllSucceeded) { return new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.RecoveryRequired, attempt.FailureReason ?? "Fish Warehouse capture prerequisites were not all satisfied."); } return new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Captured); } public static FishWarehousePropertyCaptureResult EvaluateLoadCompleteRetry(FishWarehousePropertyCaptureAttempt attempt) { if (!attempt.AllSucceeded) { return new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Degraded, attempt.FailureReason ?? "Fish Warehouse capture recovery prerequisites were not all satisfied."); } return new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Captured); } public static bool PermitsReplay(FishWarehousePropertyCaptureResult result) { return result.State == FishWarehousePropertyCaptureState.Captured; } public static bool EnablesWriteGuard(FishWarehousePropertyCaptureResult result) { return result.State == FishWarehousePropertyCaptureState.Degraded; } } public sealed class FishWarehousePropertyCaptureService { private readonly IFishWarehousePropertySnapshotStore _snapshotStore; private readonly FishWarehouseNativePropertySnapshotInspector _inspector; private readonly FishWarehousePersistenceService _persistenceService; private readonly FishWarehouseReplayLifecycle _replayLifecycle; private readonly Action _log; private FishWarehousePropertyCaptureAttempt _latestAttempt = new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false, "Fish Warehouse capture has not run."); private FishWarehouseReplayCheckpoint? _persistedCapturedCheckpoint; private string? _generationId; private FishWarehousePropertyCaptureResult _latestResult = new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Inactive); public FishWarehousePropertyCaptureState State => _latestResult.State; public PropertyData? CapturedPropertyData { get; private set; } public IReadOnlyList CapturedEmployeeRecords { get; private set; } = Array.Empty(); public string? GenerationId { get { string generationId = _generationId; if (generationId == null) { FishWarehouseReplayCheckpoint fishWarehouseReplayCheckpoint = _persistenceService.CurrentState?.Replay; if ((object)fishWarehouseReplayCheckpoint == null || fishWarehouseReplayCheckpoint.Phase != FishWarehouseReplayPhase.Failed) { return null; } generationId = fishWarehouseReplayCheckpoint.GenerationId; } return generationId; } } public bool IsDegraded { get; private set; } public FishWarehousePropertyCaptureService(IFishWarehousePropertySnapshotStore snapshotStore, FishWarehouseNativePropertySnapshotInspector inspector, FishWarehousePersistenceService persistenceService, FishWarehouseReplayLifecycle replayLifecycle, Action? log = null) { _snapshotStore = snapshotStore; _inspector = inspector; _persistenceService = persistenceService; _replayLifecycle = replayLifecycle; _log = log ?? ((Action)delegate { }); } public static IReadOnlyList CreateEmployeeReplayDescriptors(IReadOnlyList records) { return (records ?? throw new ArgumentNullException("records")).Select((FishWarehouseNativePropertyEmployeeSnapshot record) => new FishWarehouseEmployeeReplayDescriptor(record.Guid, record.DataType, record.Identity, record.RawJson)).ToArray(); } public FishWarehousePropertyCaptureResult TryCapture(PropertyData propertyData, string saveFolder, string generationId) { if (propertyData == null) { return RecordInitialFailure(saveFolder, generationId, FailureAttempt("typed property payload was unavailable."), null, null); } if (!string.Equals(propertyData.PropertyCode, "oc_fishwarehouse", StringComparison.Ordinal)) { return SetResult(new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Inactive)); } if (string.IsNullOrWhiteSpace(generationId)) { return RecordInitialFailure(saveFolder, generationId, FailureAttempt("capture generation was unavailable."), null, null); } if (State == FishWarehousePropertyCaptureState.Captured && string.Equals(_generationId, generationId, StringComparison.Ordinal) && CapturedPropertyData == propertyData) { return _latestResult; } _generationId = generationId; CapturedPropertyData = propertyData; FishWarehouseProtectedSnapshot protectedSnapshot; FishWarehouseNativePropertySnapshot inspection; byte[] nativeBytes; FishWarehousePropertyCaptureAttempt fishWarehousePropertyCaptureAttempt = TryCaptureNativeProperty(saveFolder, propertyData, generationId, out protectedSnapshot, out inspection, out nativeBytes); if (!fishWarehousePropertyCaptureAttempt.ByteProtectionSucceeded || !fishWarehousePropertyCaptureAttempt.InspectionSucceeded || !fishWarehousePropertyCaptureAttempt.TypedPayloadRetentionSucceeded) { return RecordInitialFailure(saveFolder, generationId, fishWarehousePropertyCaptureAttempt, protectedSnapshot, nativeBytes, inspection); } CapturedEmployeeRecords = inspection.Employees; if (!TryBuildCapturedCheckpoint(saveFolder, generationId, protectedSnapshot, inspection, nativeBytes, out FishWarehouseReplayCheckpoint checkpoint, out string failureReason)) { return RecordInitialFailure(saveFolder, generationId, fishWarehousePropertyCaptureAttempt with { FailureReason = failureReason }, protectedSnapshot, nativeBytes, inspection); } FishWarehouseReplayCheckpoint currentCheckpoint = _replayLifecycle.CurrentCheckpoint; bool flag = (object)currentCheckpoint != null && currentCheckpoint.Phase == FishWarehouseReplayPhase.Failed && string.Equals(currentCheckpoint.GenerationId, checkpoint.GenerationId, StringComparison.Ordinal) && string.Equals(currentCheckpoint.SaveFolderKey, checkpoint.SaveFolderKey, StringComparison.Ordinal); if (flag) { if (!_persistenceService.TryPersistRecoveredCaptureCheckpoint(checkpoint)) { return RecordInitialFailure(saveFolder, generationId, fishWarehousePropertyCaptureAttempt with { FailureReason = "Fish Warehouse recovered capture checkpoint persistence failed." }, protectedSnapshot, nativeBytes, inspection); } } else { if (!_replayLifecycle.Begin(checkpoint, out string failureReason2)) { return RecordInitialFailure(saveFolder, generationId, fishWarehousePropertyCaptureAttempt with { FailureReason = failureReason2 }, protectedSnapshot, nativeBytes, inspection); } if (!_persistenceService.TryPersistReplayCheckpoint(checkpoint)) { return RecordInitialFailure(saveFolder, generationId, fishWarehousePropertyCaptureAttempt with { FailureReason = "Fish Warehouse capture checkpoint persistence failed." }, protectedSnapshot, nativeBytes, inspection); } } _latestAttempt = fishWarehousePropertyCaptureAttempt with { CheckpointPersistenceSucceeded = true, FailureReason = null }; _persistedCapturedCheckpoint = checkpoint; if (flag && !_replayLifecycle.TryRecoverCaptureFailure(checkpoint, out string failureReason3)) { return RecordInitialFailure(saveFolder, generationId, fishWarehousePropertyCaptureAttempt with { FailureReason = (failureReason3 ?? "Fish Warehouse failed capture recovery.") }, protectedSnapshot, nativeBytes, inspection); } IsDegraded = false; return SetResult(FishWarehousePropertyCapturePolicy.EvaluateInitialCapture(_latestAttempt)); } internal FishWarehousePropertyCaptureResult RecordCaptureFailure(string saveFolder, string generationId, string failureReason) { _generationId = generationId; CapturedPropertyData = null; return RecordInitialFailure(saveFolder, generationId, FailureAttempt(failureReason), null, null); } public FishWarehousePropertyCaptureResult TryRecoverAtLoadComplete(string saveFolder) { if (State != FishWarehousePropertyCaptureState.RecoveryRequired) { return _latestResult; } if (CapturedPropertyData == null || string.IsNullOrWhiteSpace(_generationId)) { return RecordRetryFailure(FailureAttempt("typed property payload was unavailable for capture recovery.")); } FishWarehouseProtectedSnapshot protectedSnapshot; FishWarehouseNativePropertySnapshot inspection; byte[] nativeBytes; FishWarehousePropertyCaptureAttempt fishWarehousePropertyCaptureAttempt = TryCaptureNativeProperty(saveFolder, CapturedPropertyData, _generationId, out protectedSnapshot, out inspection, out nativeBytes); if (!fishWarehousePropertyCaptureAttempt.ByteProtectionSucceeded || !fishWarehousePropertyCaptureAttempt.InspectionSucceeded || !fishWarehousePropertyCaptureAttempt.TypedPayloadRetentionSucceeded) { return RecordRetryFailure(fishWarehousePropertyCaptureAttempt); } CapturedEmployeeRecords = inspection.Employees; if (!TryBuildCapturedCheckpoint(saveFolder, _generationId, protectedSnapshot, inspection, nativeBytes, out FishWarehouseReplayCheckpoint checkpoint, out string failureReason)) { return RecordRetryFailure(fishWarehousePropertyCaptureAttempt with { FailureReason = failureReason }); } if (!_persistenceService.TryPersistRecoveredCaptureCheckpoint(checkpoint)) { return RecordRetryFailure(fishWarehousePropertyCaptureAttempt with { FailureReason = "Fish Warehouse recovered capture checkpoint persistence failed." }); } _latestAttempt = fishWarehousePropertyCaptureAttempt with { CheckpointPersistenceSucceeded = true, FailureReason = null }; _persistedCapturedCheckpoint = checkpoint; if (!_replayLifecycle.TryRecoverCaptureFailure(checkpoint, out string failureReason2)) { _persistenceService.TryPersistReplayCheckpoint(_replayLifecycle.CurrentCheckpoint); return RecordRetryFailure(_latestAttempt with { FailureReason = failureReason2 }); } IsDegraded = false; return SetResult(FishWarehousePropertyCapturePolicy.EvaluateLoadCompleteRetry(_latestAttempt)); } public FishWarehouseCaptureRecoveryPrerequisites GetRecoveryPrerequisites(FishWarehouseReplayCheckpoint checkpoint) { if (!(_persistedCapturedCheckpoint == checkpoint)) { return new FishWarehouseCaptureRecoveryPrerequisites(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false); } return _latestAttempt.ToRecoveryPrerequisites(); } public void ResetRuntimeReferences() { CapturedPropertyData = null; CapturedEmployeeRecords = Array.Empty(); _persistedCapturedCheckpoint = null; _generationId = null; _latestAttempt = new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false, "Fish Warehouse capture has not run for this load."); _replayLifecycle.ResetForNewCaptureGeneration(); if (!IsDegraded) { _latestResult = new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Inactive); } } public void Reset() { CapturedPropertyData = null; CapturedEmployeeRecords = Array.Empty(); _persistedCapturedCheckpoint = null; _generationId = null; _latestAttempt = new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false, "Fish Warehouse capture has not run."); _latestResult = new FishWarehousePropertyCaptureResult(FishWarehousePropertyCaptureState.Inactive); IsDegraded = false; } private FishWarehousePropertyCaptureAttempt TryCaptureNativeProperty(string saveFolder, PropertyData propertyData, string generationId, out FishWarehouseProtectedSnapshot? protectedSnapshot, out FishWarehouseNativePropertySnapshot? inspection, out byte[] nativeBytes) { protectedSnapshot = null; inspection = null; nativeBytes = Array.Empty(); bool typedPayloadRetentionSucceeded = propertyData != null && CapturedPropertyData == propertyData; if (!TryReadNativePropertyBytes(saveFolder, out nativeBytes, out string failureReason)) { return new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: false, InspectionSucceeded: false, typedPayloadRetentionSucceeded, CheckpointPersistenceSucceeded: false, failureReason); } if (!_snapshotStore.TryProtect(saveFolder, generationId, nativeBytes, out FishWarehouseProtectedSnapshot snapshot, out string failureReason2)) { return new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: false, InspectionSucceeded: false, typedPayloadRetentionSucceeded, CheckpointPersistenceSucceeded: false, failureReason2); } protectedSnapshot = snapshot; if (!_inspector.TryInspect(nativeBytes, out FishWarehouseNativePropertySnapshot snapshot2, out string failureReason3)) { return new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: true, InspectionSucceeded: false, typedPayloadRetentionSucceeded, CheckpointPersistenceSucceeded: false, failureReason3); } inspection = snapshot2; if (!string.Equals(inspection.PropertyCode, "oc_fishwarehouse", StringComparison.Ordinal)) { return new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: true, InspectionSucceeded: false, typedPayloadRetentionSucceeded, CheckpointPersistenceSucceeded: false, "Fish Warehouse native property inspection returned a different property code."); } return new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: true, InspectionSucceeded: true, typedPayloadRetentionSucceeded, CheckpointPersistenceSucceeded: false, null); } private FishWarehousePropertyCaptureResult RecordInitialFailure(string saveFolder, string generationId, FishWarehousePropertyCaptureAttempt attempt, FishWarehouseProtectedSnapshot? protectedSnapshot, byte[]? nativeBytes, FishWarehouseNativePropertySnapshot? inspection = null) { string text = attempt.FailureReason ?? "Fish Warehouse native property capture failed."; if (TryBuildCapturedCheckpoint(saveFolder, generationId, protectedSnapshot, inspection, nativeBytes, out FishWarehouseReplayCheckpoint checkpoint, out string failureReason) && _replayLifecycle.Begin(checkpoint, out failureReason)) { _replayLifecycle.Fail(FishWarehouseReplayPhase.Captured, text); attempt = attempt with { CheckpointPersistenceSucceeded = _persistenceService.TryPersistReplayCheckpoint(_replayLifecycle.CurrentCheckpoint), FailureReason = text }; } _latestAttempt = attempt; _persistedCapturedCheckpoint = null; _log("WARNING: Fish Warehouse native property capture failed; native load remains suppressed and recovery is required: " + text); return SetResult(FishWarehousePropertyCapturePolicy.EvaluateInitialCapture(attempt)); } private FishWarehousePropertyCaptureResult RecordRetryFailure(FishWarehousePropertyCaptureAttempt attempt) { _latestAttempt = attempt; _persistedCapturedCheckpoint = null; IsDegraded = true; FishWarehousePropertyCaptureResult fishWarehousePropertyCaptureResult = FishWarehousePropertyCapturePolicy.EvaluateLoadCompleteRetry(attempt); _log("WARNING: Fish Warehouse capture recovery failed; normal Fish Warehouse saves are blocked to preserve the native property file: " + fishWarehousePropertyCaptureResult.FailureReason); return SetResult(fishWarehousePropertyCaptureResult); } private static FishWarehousePropertyCaptureAttempt FailureAttempt(string reason) { return new FishWarehousePropertyCaptureAttempt(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false, reason); } private static bool TryBuildCapturedCheckpoint(string saveFolder, string generationId, FishWarehouseProtectedSnapshot? protectedSnapshot, FishWarehouseNativePropertySnapshot? inspection, byte[]? nativeBytes, out FishWarehouseReplayCheckpoint checkpoint, out string? failureReason) { checkpoint = null; failureReason = null; if (string.IsNullOrWhiteSpace(generationId)) { failureReason = "Fish Warehouse capture generation was unavailable."; return false; } try { FishWarehouseProtectedSnapshot fishWarehouseProtectedSnapshot = protectedSnapshot ?? new FishWarehouseProtectedSnapshot(generationId, FishWarehouseSaveFolderKey.Create(saveFolder), "OrganizedCrime/fish-warehouse.snapshot.json", Convert.ToHexString(SHA256.HashData(nativeBytes ?? Array.Empty())), (nativeBytes != null) ? nativeBytes.Length : 0); checkpoint = new FishWarehouseReplayCheckpoint(generationId, FishWarehouseSaveFolderKey.Create(saveFolder), FishWarehouseReplayPhase.Captured, fishWarehouseProtectedSnapshot.RelativePath, fishWarehouseProtectedSnapshot.Sha256, inspection?.Objects.Count ?? 0, inspection?.Employees.Count ?? 0); return true; } catch (Exception ex) when (((ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse capture checkpoint could not be created: " + ex.Message; return false; } } private static bool TryReadNativePropertyBytes(string saveFolder, out byte[] bytes, out string? failureReason) { bytes = Array.Empty(); if (!FishWarehouseSavePath.TryResolveNativePropertyPath(saveFolder, RuntimePropertyDefinition.FishWarehouse, out string propertyPath, out failureReason)) { return false; } try { bytes = File.ReadAllBytes(propertyPath); return true; } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is ArgumentException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse native property bytes could not be read: " + ex.Message; return false; } } private FishWarehousePropertyCaptureResult SetResult(FishWarehousePropertyCaptureResult result) { _latestResult = result; return result; } } internal static class FishWarehousePropertyLoadCapturePatch { private static Func _isAuthoritativeHost = () => false; private static Func _activeSaveFolder = () => (string?)null; private static FishWarehousePropertyCaptureService? _captureService; private static Action _log = delegate { }; public static void Apply(Harmony harmony, Func isAuthoritativeHost, Func activeSaveFolder, FishWarehousePropertyCaptureService captureService, Action? log = null) { //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Expected O, but got Unknown _isAuthoritativeHost = isAuthoritativeHost; _activeSaveFolder = activeSaveFolder; _captureService = captureService; _log = log ?? ((Action)delegate { }); MethodInfo methodInfo = AccessTools.Method(typeof(PropertyLoader), "Load", new Type[2] { typeof(PropertyData), typeof(string) }, (Type[])null) ?? throw new MissingMethodException(typeof(PropertyLoader).FullName, "Load"); MethodInfo methodInfo2 = AccessTools.Method(typeof(FishWarehousePropertyLoadCapturePatch), "Prefix", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(FishWarehousePropertyLoadCapturePatch).FullName, "Prefix"); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } private static bool Prefix(PropertyData __0, string __1) { if (!_isAuthoritativeHost()) { return true; } string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { return true; } string propertyCode; try { propertyCode = __0.PropertyCode; } catch (Exception exception) { return HandleAuthoritativePropertyCodeReadFailure(text, _captureService, exception); } if (FishWarehousePropertyCapturePolicy.ShouldPassThrough(isAuthoritativeHost: true, propertyCode, text)) { return true; } try { FishWarehousePropertyCaptureService captureService = _captureService; if (captureService == null) { _log("WARNING: Fish Warehouse native property load was suppressed because the capture service was unavailable."); return false; } string generationId = captureService.GenerationId ?? Guid.NewGuid().ToString("N"); FishWarehousePropertyCaptureResult fishWarehousePropertyCaptureResult = captureService.TryCapture(__0, text, generationId); if (fishWarehousePropertyCaptureResult.State != FishWarehousePropertyCaptureState.Captured) { _log($"WARNING: Fish Warehouse native property load remains suppressed ({fishWarehousePropertyCaptureResult.State}): {fishWarehousePropertyCaptureResult.FailureReason}"); } return false; } catch (Exception value) { _log($"WARNING: Fish Warehouse native property capture threw after the target was identified; native load remains suppressed: {value}"); return false; } } internal static bool HandleAuthoritativePropertyCodeReadFailure(string? activeSaveFolder, FishWarehousePropertyCaptureService? captureService, Exception exception) { if (string.IsNullOrWhiteSpace(activeSaveFolder)) { return true; } if (captureService == null) { _log("WARNING: Fish Warehouse native property load was suppressed because PropertyCode could not be read and the capture service was unavailable."); return false; } try { string generationId = captureService.GenerationId ?? Guid.NewGuid().ToString("N"); FishWarehousePropertyCaptureResult fishWarehousePropertyCaptureResult = captureService.RecordCaptureFailure(activeSaveFolder, generationId, "Fish Warehouse native property discriminator PropertyCode could not be read: " + exception.Message); _log($"WARNING: Fish Warehouse native property load remains suppressed ({fishWarehousePropertyCaptureResult.State}) because PropertyCode could not be read: {fishWarehousePropertyCaptureResult.FailureReason}"); } catch (Exception value) { _log($"WARNING: Fish Warehouse native property load remains suppressed because PropertyCode could not be read and capture failure recording threw: {value}"); } return false; } private static void VerifyInstalledInterop(PropertyLoader loader, PropertyData propertyData, Property property, DynamicSaveData dynamicSaveData, string saveFolder) { loader.Load(propertyData, saveFolder); property.WriteData(saveFolder); _ = propertyData.PropertyCode; _ = propertyData.IsOwned; _ = propertyData.Objects; _ = propertyData.Employees; _ = ((SaveData)dynamicSaveData).DataType; _ = dynamicSaveData.BaseData; _ = dynamicSaveData.AdditionalDatas; } public static void Reset() { _isAuthoritativeHost = () => false; _activeSaveFolder = () => (string?)null; _captureService = null; _log = delegate { }; } } public enum FishWarehousePropertyReplayState { Pending, Succeeded, Failed } public sealed record FishWarehousePropertyReplayResult(FishWarehousePropertyReplayState State, int ExpectedObjectCount, int ResolvedObjectCount, int LoadedObjectCount, string? FailureReason); public sealed class FishWarehousePropertyReplayHost { private sealed record PlannedObject(FishWarehouseSavedObjectDescriptor Descriptor, int LoaderLoadOrder, string GuidKey); private readonly IFishWarehouseNativePropertyReplayAdapter _adapter; private readonly IReadOnlyList _descriptors; private readonly Action? _placeFallbackEmployeeHome; private readonly Action _log; private readonly HashSet _loadedPayloadIndexes = new HashSet(); private bool _fallbackPlaced; public FishWarehousePropertyReplayResult Result { get; private set; } public FishWarehousePropertyReplayHost(IFishWarehouseNativePropertyReplayAdapter adapter, IReadOnlyList descriptors, Action? placeFallbackEmployeeHome = null, Action? log = null) { _adapter = adapter ?? throw new ArgumentNullException("adapter"); _descriptors = descriptors ?? throw new ArgumentNullException("descriptors"); _placeFallbackEmployeeHome = placeFallbackEmployeeHome; _log = log ?? ((Action)delegate { }); Result = new FishWarehousePropertyReplayResult(FishWarehousePropertyReplayState.Pending, UniqueGuidCount(), 0, 0, null); } public FishWarehouseEmployeeReplayResult ReplayEmployees(FishWarehouseEmployeeReplayHost employeeReplay, bool navigationReady) { if (employeeReplay == null) { throw new ArgumentNullException("employeeReplay"); } if (Result.State != FishWarehousePropertyReplayState.Succeeded) { return employeeReplay.Result; } return employeeReplay.Replay(navigationReady); } public FishWarehousePropertyReplayResult Replay() { if (Result.State == FishWarehousePropertyReplayState.Succeeded) { return Result; } string failureReason; IReadOnlyList readOnlyList = BuildPlan(out failureReason); if (failureReason != null) { return Fail(failureReason); } foreach (PlannedObject item in readOnlyList) { if (!_adapter.IsGuidRegistered(item.Descriptor.Guid) && _loadedPayloadIndexes.Add(item.Descriptor.PayloadIndex)) { try { _adapter.LoadObject(item.Descriptor.PayloadIndex); } catch (Exception ex) { return Fail($"loader-failure (index={item.Descriptor.PayloadIndex}, type={item.Descriptor.DataType}, guid={item.Descriptor.Guid}): {ex.Message}"); } } } List list = new List(readOnlyList.Count); foreach (PlannedObject item2 in readOnlyList) { FishWarehouseRestoredObjectObservation fishWarehouseRestoredObjectObservation; try { fishWarehouseRestoredObjectObservation = _adapter.ObserveObject(item2.Descriptor.Guid); } catch (Exception ex2) { return Fail($"observation-failure (index={item2.Descriptor.PayloadIndex}, guid={item2.Descriptor.Guid}): {ex2.Message}"); } if (!fishWarehouseRestoredObjectObservation.IsResolved) { return Fail($"unresolved-guid (index={item2.Descriptor.PayloadIndex}, guid={item2.Descriptor.Guid})"); } if (!string.Equals(fishWarehouseRestoredObjectObservation.ParentPropertyCode, RuntimePropertyDefinition.FishWarehouse.PropertyCode, StringComparison.Ordinal)) { return Fail($"wrong-parent-property (index={item2.Descriptor.PayloadIndex}, guid={item2.Descriptor.Guid}, ParentPropertyCode={fishWarehouseRestoredObjectObservation.ParentPropertyCode ?? "null"})"); } list.Add(fishWarehouseRestoredObjectObservation); } if (!_fallbackPlaced && !readOnlyList.Any((PlannedObject entry) => entry.Descriptor.IsSavedEmployeeHome) && !list.Any((FishWarehouseRestoredObjectObservation observation) => observation.IsEmployeeHome)) { _fallbackPlaced = true; _placeFallbackEmployeeHome?.Invoke(); } Result = new FishWarehousePropertyReplayResult(FishWarehousePropertyReplayState.Succeeded, readOnlyList.Count, list.Count, _loadedPayloadIndexes.Count, null); _log($"Fish Warehouse native replay summary: {Result.ResolvedObjectCount}/{Result.ExpectedObjectCount} resolved, loaded={Result.LoadedObjectCount}."); return Result; } private IReadOnlyList BuildPlan(out string? failureReason) { failureReason = null; List list = new List(); foreach (FishWarehouseSavedObjectDescriptor descriptor in _descriptors) { if (!Guid.TryParse(descriptor.Guid, out var result)) { failureReason = $"malformed-guid (index={descriptor.PayloadIndex}, type={descriptor.DataType}, guid={descriptor.Guid})"; return Array.Empty(); } if (!_adapter.TryGetObjectLoaderLoadOrder(descriptor.DataType, out var loadOrder)) { failureReason = $"missing-loader (index={descriptor.PayloadIndex}, type={descriptor.DataType}, guid={descriptor.Guid})"; return Array.Empty(); } list.Add(new PlannedObject(descriptor, loadOrder, result.ToString("D"))); } PlannedObject[] source = (from entry in list orderby entry.LoaderLoadOrder, entry.Descriptor.SavedLoadOrder, entry.Descriptor.IsDependent ? 1 : 0, entry.Descriptor.PayloadIndex select entry).ToArray(); HashSet seenGuids = new HashSet(StringComparer.Ordinal); return source.Where((PlannedObject entry) => seenGuids.Add(entry.GuidKey)).ToArray(); } private FishWarehousePropertyReplayResult Fail(string reason) { Result = new FishWarehousePropertyReplayResult(FishWarehousePropertyReplayState.Failed, UniqueGuidCount(), 0, _loadedPayloadIndexes.Count, reason); _log("Fish Warehouse native replay failed: " + reason); return Result; } private int UniqueGuidCount() { return _descriptors.Select((FishWarehouseSavedObjectDescriptor descriptor) => CanonicalGuidKey(descriptor.Guid)).Distinct(StringComparer.Ordinal).Count(); } private static string CanonicalGuidKey(string savedGuid) { if (!Guid.TryParse(savedGuid, out var result)) { return savedGuid; } return result.ToString("D"); } } public sealed class FishWarehouseRuntimeService { private readonly IRuntimePropertyHost _host; private readonly Func _isReady; private readonly Func _tryPlaceEmployeeHome; private readonly Action _log; private bool _restoreRequested; private bool _restoreEmployeeHomeRequested; private FishWarehouseContinuousNavigationState _lastReportedEmployeeNavigationState; private bool _unlocked; private bool _owned; private bool _propertyOwnershipApplied; private bool _employeeHomePlaced; private bool _persistenceObjectReplayRequired; public RuntimePropertyLifecycleState State { get; private set; } public bool OwnershipUnlocked => _owned; public FishWarehouseContinuousNavigationState EmployeeNavigationState { get { if (State != RuntimePropertyLifecycleState.Spawned || !_propertyOwnershipApplied) { return FishWarehouseContinuousNavigationState.Inactive; } return _host.EmployeeNavigationState; } } public bool EmployeeNavigationReady { get { if (State == RuntimePropertyLifecycleState.Spawned && _propertyOwnershipApplied) { return _host.EmployeeNavigationReady; } return false; } } public FishWarehouseRuntimeService(IRuntimePropertyHost host, Func isReady, Func? tryPlaceEmployeeHome = null, Action? log = null) { _host = host; _isReady = isReady; _tryPlaceEmployeeHome = tryPlaceEmployeeHome ?? ((Func)(() => false)); _log = log ?? ((Action)delegate { }); } public FishWarehouseSaveState CaptureSaveState() { return new FishWarehouseSaveState(2, "oc_fishwarehouse", _unlocked, _owned, _employeeHomePlaced, _host.EmployeeNavigationAgentTypeId, null, Array.Empty()); } public void ApplyLoadedSaveState(FishWarehouseSaveState state) { _unlocked = state.Unlocked; _owned = state.Owned; _employeeHomePlaced = state.EmployeeHomePlaced; FishWarehouseReplayCheckpoint replay = state.Replay; _persistenceObjectReplayRequired = (object)replay != null && replay.Phase >= FishWarehouseReplayPhase.Captured && replay.Phase <= FishWarehouseReplayPhase.DeliveriesReleased; _host.ConfigurePersistenceReplay(_persistenceObjectReplayRequired, state.EmployeeAgentTypeId); _propertyOwnershipApplied = false; _restoreRequested = false; _restoreEmployeeHomeRequested = false; } public void RequestLoadedOwnedRestore() { if (_owned) { _restoreRequested = true; _restoreEmployeeHomeRequested = _employeeHomePlaced; } } public bool PrepareForLoad() { _restoreRequested = false; _restoreEmployeeHomeRequested = false; _lastReportedEmployeeNavigationState = FishWarehouseContinuousNavigationState.Inactive; if (State == RuntimePropertyLifecycleState.Spawned) { RuntimePropertyHostResult runtimePropertyHostResult = Stop(); if (!runtimePropertyHostResult.CleanupPassed || State != RuntimePropertyLifecycleState.Unavailable) { _log("Fish Warehouse runtime Property could not prepare for save load: " + (runtimePropertyHostResult.FailureReason ?? "cleanup was incomplete")); return false; } } else { RuntimePropertyLifecycleState state = State; if ((state == RuntimePropertyLifecycleState.Starting || state == RuntimePropertyLifecycleState.Unloading) ? true : false) { _log($"Fish Warehouse runtime Property could not prepare for save load while in state {State}."); return false; } state = State; if ((state == RuntimePropertyLifecycleState.Ready || state == RuntimePropertyLifecycleState.Failed) ? true : false) { State = RuntimePropertyLifecycleState.Unavailable; } } _unlocked = false; _owned = false; _propertyOwnershipApplied = false; _employeeHomePlaced = false; _persistenceObjectReplayRequired = false; return true; } public void Update() { if (State == RuntimePropertyLifecycleState.Spawned && _propertyOwnershipApplied) { _host.UpdateOwnedFeatures(); ReportEmployeeNavigationState(); } if (State == RuntimePropertyLifecycleState.Unavailable) { if (!_isReady()) { return; } TransitionTo(RuntimePropertyLifecycleState.Ready); _log("Fish Warehouse runtime Property is ready for developer lifecycle validation."); } if (State == RuntimePropertyLifecycleState.Ready && _restoreRequested) { _restoreRequested = false; _log("Restoring the owned Fish Warehouse runtime Property from the loaded save state."); if (Start().Succeeded) { TryUnlockDeveloperLifecycle(); } } } public RuntimePropertyHostResult ToggleDeveloperLifecycle() { if (State == RuntimePropertyLifecycleState.Ready) { return Start(); } if (State == RuntimePropertyLifecycleState.Spawned) { return Stop(); } return RuntimePropertyHostResult.Failure("state", $"Developer lifecycle toggle is unavailable in state {State}."); } public bool TryUnlockDeveloperLifecycle() { _restoreRequested = false; if (State != RuntimePropertyLifecycleState.Spawned) { _log($"Fish Warehouse ownership unlock is unavailable in state {State}."); return false; } if (_propertyOwnershipApplied) { return true; } if (_host.TrySetOwned()) { _unlocked = true; _owned = true; _propertyOwnershipApplied = true; _lastReportedEmployeeNavigationState = FishWarehouseContinuousNavigationState.Inactive; if (_restoreEmployeeHomeRequested && !_persistenceObjectReplayRequired) { _restoreEmployeeHomeRequested = false; TryPlaceEmployeeHome(); } return true; } TransitionTo(RuntimePropertyLifecycleState.Unloading); RuntimePropertyHostResult runtimePropertyHostResult = _host.Stop(); TransitionTo(RuntimePropertyLifecycleState.Failed); _log(runtimePropertyHostResult.CleanupPassed ? "Fish Warehouse ownership unlock failed; runtime Property was cleaned up and the service is terminal for this process." : "Fish Warehouse ownership unlock and cleanup failed; the service is terminal for this process."); return false; } public bool TryPlaceEmployeeHome() { if (State != RuntimePropertyLifecycleState.Spawned || !_propertyOwnershipApplied) { _log("Fish Warehouse employee-home placement is unavailable until the runtime Property is spawned and owned."); return false; } bool num = _tryPlaceEmployeeHome(); if (num) { _employeeHomePlaced = true; } if (!num) { _log("Fish Warehouse employee-home placement did not pass its guarded runtime checks."); } return num; } public void MarkPersistenceObjectsReplayed() { _host.MarkPersistenceObjectsReplayed(); _persistenceObjectReplayRequired = false; _restoreEmployeeHomeRequested = false; } public void MarkPersistenceEmployeeHomeRestored() { _employeeHomePlaced = true; } private RuntimePropertyHostResult Start() { TransitionTo(RuntimePropertyLifecycleState.Starting); RuntimePropertyHostResult runtimePropertyHostResult = _host.Start(RuntimePropertyDefinition.FishWarehouse); if (runtimePropertyHostResult.Succeeded) { _lastReportedEmployeeNavigationState = FishWarehouseContinuousNavigationState.Inactive; TransitionTo(RuntimePropertyLifecycleState.Spawned); _log("Fish Warehouse runtime Property is spawned."); } else { TransitionTo(RuntimePropertyLifecycleState.Failed); _log("Fish Warehouse runtime Property start failed at " + runtimePropertyHostResult.Stage + ": " + runtimePropertyHostResult.FailureReason); } return runtimePropertyHostResult; } private RuntimePropertyHostResult Stop() { TransitionTo(RuntimePropertyLifecycleState.Unloading); _log("Unloading Fish Warehouse runtime Property."); RuntimePropertyHostResult runtimePropertyHostResult = _host.Stop(); if (string.Equals(runtimePropertyHostResult.Stage, "occupied-loading-docks", StringComparison.Ordinal)) { TransitionTo(RuntimePropertyLifecycleState.Spawned); _log("Fish Warehouse unload refused: " + runtimePropertyHostResult.FailureReason); return runtimePropertyHostResult; } if (string.Equals(runtimePropertyHostResult.Stage, "pending-native-navigation-cleanup", StringComparison.Ordinal)) { TransitionTo(RuntimePropertyLifecycleState.Spawned); _log("Fish Warehouse unload deferred: " + runtimePropertyHostResult.FailureReason); return runtimePropertyHostResult; } if (runtimePropertyHostResult.CleanupPassed) { TransitionTo(RuntimePropertyLifecycleState.Unavailable); _log("Fish Warehouse runtime Property unloaded cleanly."); } else { TransitionTo(RuntimePropertyLifecycleState.Failed); _log("Fish Warehouse runtime Property cleanup failed: " + runtimePropertyHostResult.FailureReason); } return runtimePropertyHostResult; } private void ReportEmployeeNavigationState() { FishWarehouseContinuousNavigationState employeeNavigationState = EmployeeNavigationState; if (employeeNavigationState != _lastReportedEmployeeNavigationState) { _lastReportedEmployeeNavigationState = employeeNavigationState; switch (employeeNavigationState) { case FishWarehouseContinuousNavigationState.Ready: _log("Fish Warehouse employee navigation is ready."); break; case FishWarehouseContinuousNavigationState.Failed: _log("Fish Warehouse employee navigation failed closed; property ownership and existing features remain active."); break; } } } private void TransitionTo(RuntimePropertyLifecycleState next) { if (!RuntimePropertyLifecycle.CanTransition(State, next)) { throw new InvalidOperationException($"Invalid runtime Property transition: {State} -> {next}."); } State = next; } } public enum FishWarehouseEmployeePrimaryLoadResult { Started, LoaderUnavailable, Failed } public enum FishWarehouseEmployeeObservationResult { Ready, Pending, Failed } public enum FishWarehouseEmployeeFallbackCreationResult { Started, Failed } public enum FishWarehouseEmployeeFallbackConfigurationResult { Configured, Pending, Failed } public enum FishWarehouseEmployeeActiveMoveRestoreResult { NotRequired, Pending, Restored, Failed } public sealed record FishWarehouseEmployeeReplayDescriptor(string Guid, string DataType, string Identity, string RawJson) { public bool IsSupportedPackager { get { if (string.Equals(DataType, "PackagerData", StringComparison.Ordinal)) { return string.Equals(Identity, "Packager", StringComparison.Ordinal); } return false; } } public bool TryParseExpectedState(out FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason) { expected = null; failureReason = null; try { using JsonDocument jsonDocument = JsonDocument.Parse(RawJson); JsonElement rootElement = jsonDocument.RootElement; string a = ReadRequiredString(rootElement, "DataType"); if (!string.Equals(a, DataType, StringComparison.Ordinal)) { throw new JsonException("data-type-mismatch"); } JsonElement jsonElement = ReadWrappedObject(rootElement, "BaseData"); if (!System.Guid.TryParse(ReadRequiredString(jsonElement, "GUID"), out var result) || !System.Guid.TryParse(Guid, out var result2) || result != result2) { throw new JsonException("guid-mismatch"); } object obj; if (!jsonElement.TryGetProperty("Identity", out var value)) { if (!string.Equals(a, "PackagerData", StringComparison.Ordinal)) { throw new JsonException("Identity-missing"); } obj = "Packager"; } else { obj = ReadRequiredString(jsonElement, "Identity"); } string text = (string)obj; string b = ((string.IsNullOrWhiteSpace(Identity) && string.Equals(DataType, "PackagerData", StringComparison.Ordinal)) ? "Packager" : Identity); if (!string.Equals(text, b, StringComparison.Ordinal)) { throw new JsonException("identity-mismatch"); } JsonElement parent = ReadObject(jsonElement, "Position"); JsonElement parent2 = ReadObject(jsonElement, "Rotation"); JsonElement parent3 = ReadConfiguration(rootElement); string inventoryJson = ReadOptionalAdditionalData(rootElement, "Inventory"); string homeGuid = (jsonElement.TryGetProperty("BedGUID", out value) ? ReadOptionalString(jsonElement, "BedGUID") : ReadOptionalString(ReadObject(parent3, "Bed"), "ObjectGUID")); IReadOnlyList readOnlyList = (parent3.TryGetProperty("StationGUIDs", out value) ? ReadStringArray(parent3, "StationGUIDs") : ReadStringArray(ReadObject(parent3, "Stations"), "ObjectGUIDs")); if (readOnlyList.Count > 3) { throw new JsonException("station-count-exceeds-max"); } FishWarehouseEmployeeMoveItemState moveItem = null; if (jsonElement.TryGetProperty("MoveItemData", out var value2) && value2.ValueKind != JsonValueKind.Null && !IsIdleMoveItemData(value2)) { moveItem = new FishWarehouseEmployeeMoveItemState(ReadRequiredString(value2, "SourceGUID"), ReadRequiredString(value2, "DestinationGUID"), ReadRequiredString(value2, "TemplateItemJSON"), ReadRequiredInt(value2, "GrabbedItemQuantity")); } expected = new FishWarehouseEmployeeReplayExpectedState(result2.ToString("D"), text, ReadRequiredString(jsonElement, "ID"), ReadRequiredString(jsonElement, "FirstName"), ReadRequiredString(jsonElement, "LastName"), ReadRequiredBoolean(jsonElement, "IsMale"), ReadRequiredInt(jsonElement, "AppearanceIndex"), ReadRequiredFloat(parent, "x"), ReadRequiredFloat(parent, "y"), ReadRequiredFloat(parent, "z"), ReadRequiredFloat(parent2, "x"), ReadRequiredFloat(parent2, "y"), ReadRequiredFloat(parent2, "z"), ReadRequiredFloat(parent2, "w"), ReadPropertyCode(jsonElement), ReadRequiredBoolean(jsonElement, "PaidForToday"), moveItem, homeGuid, readOnlyList, inventoryJson); return true; } catch (Exception ex) when (((ex is JsonException || ex is InvalidOperationException || ex is FormatException) ? 1 : 0) != 0) { failureReason = ex.Message; return false; } } private static JsonElement ReadConfiguration(JsonElement root) { if (!root.TryGetProperty("AdditionalDatas", out var value) || value.ValueKind != JsonValueKind.Array) { throw new JsonException("configuration-missing"); } foreach (JsonElement item in value.EnumerateArray()) { if (item.TryGetProperty("Name", out var value2) && string.Equals(value2.GetString(), "Configuration", StringComparison.Ordinal) && item.TryGetProperty("Contents", out var value3)) { return ReadObjectValue(value3, "Configuration"); } } throw new JsonException("configuration-missing"); } private static string? ReadOptionalAdditionalData(JsonElement root, string name) { if (!root.TryGetProperty("AdditionalDatas", out var value) || value.ValueKind != JsonValueKind.Array) { return null; } foreach (JsonElement item in value.EnumerateArray()) { if (item.TryGetProperty("Name", out var value2) && string.Equals(value2.GetString(), name, StringComparison.Ordinal) && item.TryGetProperty("Contents", out var value3)) { switch (value3.ValueKind) { case JsonValueKind.String: return value3.GetString(); case JsonValueKind.Object: case JsonValueKind.Array: return value3.GetRawText(); default: throw new JsonException(name + "-invalid"); } } } return null; } private static JsonElement ReadObject(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Object) { throw new JsonException(name + "-missing"); } return value; } private static JsonElement ReadWrappedObject(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value)) { throw new JsonException(name + "-missing"); } return ReadObjectValue(value, name); } private static JsonElement ReadObjectValue(JsonElement value, string name) { if (value.ValueKind == JsonValueKind.Object) { return value; } if (value.ValueKind == JsonValueKind.String) { try { using JsonDocument jsonDocument = JsonDocument.Parse(value.GetString() ?? string.Empty); if (jsonDocument.RootElement.ValueKind == JsonValueKind.Object) { return jsonDocument.RootElement.Clone(); } } catch (JsonException) { throw new JsonException(name + "-invalid"); } } throw new JsonException(name + "-missing"); } private static string ReadPropertyCode(JsonElement baseData) { if (!baseData.TryGetProperty("PropertyCode", out var _)) { return ReadRequiredString(baseData, "AssignedProperty"); } return ReadRequiredString(baseData, "PropertyCode"); } private static bool IsIdleMoveItemData(JsonElement moveItemData) { if (moveItemData.ValueKind == JsonValueKind.Object && IsEmptyString(moveItemData, "SourceGUID") && IsEmptyString(moveItemData, "DestinationGUID") && IsEmptyString(moveItemData, "TemplateItemJSON") && moveItemData.TryGetProperty("GrabbedItemQuantity", out var value) && value.TryGetInt32(out var value2)) { return value2 == 0; } return false; } private static bool IsEmptyString(JsonElement parent, string name) { if (parent.TryGetProperty(name, out var value) && value.ValueKind == JsonValueKind.String) { return string.Equals(value.GetString(), string.Empty, StringComparison.Ordinal); } return false; } private static string ReadRequiredString(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.String || string.IsNullOrWhiteSpace(value.GetString())) { throw new JsonException(name + "-missing"); } return value.GetString(); } private static string ReadOptionalString(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.String) { throw new JsonException(name + "-missing"); } string text = value.GetString(); if (text == null || (text.Length > 0 && string.IsNullOrWhiteSpace(text))) { throw new JsonException(name + "-missing"); } return text; } private static bool ReadRequiredBoolean(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || (value.ValueKind != JsonValueKind.True && value.ValueKind != JsonValueKind.False)) { throw new JsonException(name + "-missing"); } return value.GetBoolean(); } private static int ReadRequiredInt(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || !value.TryGetInt32(out var value2)) { throw new JsonException(name + "-missing"); } return value2; } private static float ReadRequiredFloat(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || !value.TryGetSingle(out var value2)) { throw new JsonException(name + "-missing"); } return value2; } private static IReadOnlyList ReadStringArray(JsonElement parent, string name) { if (!parent.TryGetProperty(name, out var value) || value.ValueKind != JsonValueKind.Array) { throw new JsonException(name + "-missing"); } string[] array = (from jsonElement in value.EnumerateArray() select jsonElement.GetString() into result where !string.IsNullOrWhiteSpace(result) select (result)).ToArray(); if (array.Length != value.GetArrayLength()) { throw new JsonException(name + "-invalid"); } return array; } } public sealed record FishWarehouseEmployeeReplayExpectedState(string Guid, string Identity, string Id, string FirstName, string LastName, bool IsMale, int AppearanceIndex, float PositionX, float PositionY, float PositionZ, float RotationX, float RotationY, float RotationZ, float RotationW, string PropertyCode, bool PaidForToday, FishWarehouseEmployeeMoveItemState? MoveItem, string HomeGuid, IReadOnlyList StationGuids, string? InventoryJson = null); public sealed record FishWarehouseEmployeeMoveItemState(string SourceGuid, string DestinationGuid, string TemplateItemJson, int GrabbedItemQuantity); public sealed record FishWarehouseEmployeeReplayObservedState(bool IsAlive, string Guid, string Identity, string Id, string FirstName, string LastName, bool IsMale, int AppearanceIndex, float PositionX, float PositionY, float PositionZ, float RotationX, float RotationY, float RotationZ, float RotationW, string PropertyCode, bool PaidForToday, FishWarehouseEmployeeMoveItemState? MoveItem, string HomeGuid, IReadOnlyList StationGuids); public interface IFishWarehouseNativeEmployeeReplayAdapter { IReadOnlyCollection InventoryRestoredEmployeeGuids { get; } bool TryIsGuidRegistered(string savedGuid, out bool isRegistered, out string? failureReason); FishWarehouseEmployeePrimaryLoadResult TryLoadPrimary(FishWarehouseEmployeeReplayDescriptor descriptor, out string? failureReason); FishWarehouseEmployeeObservationResult TryObserveEmployee(string savedGuid, out FishWarehouseEmployeeReplayObservedState? observation, out string? failureReason); FishWarehouseEmployeeFallbackCreationResult TryCreateFallback(FishWarehouseEmployeeReplayDescriptor descriptor, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason); FishWarehouseEmployeeFallbackConfigurationResult TryConfigureFallback(FishWarehouseEmployeeReplayDescriptor descriptor, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason); FishWarehouseEmployeeActiveMoveRestoreResult TryRestoreActiveMove(FishWarehouseEmployeeReplayDescriptor descriptor, FishWarehouseEmployeeReplayExpectedState expected, out string? failureReason); } public enum FishWarehouseNativeNavigationBuildResult { Pending, Succeeded, Failed } public sealed record FishWarehouseNativeNavigationLiveEmployeeAgent(string Identity, int AgentTypeId); public enum FishWarehouseNativeNavigationEmployeeScope { PropertyAssigned, Global } public readonly record struct FishWarehouseNativeNavigationRuntimeGraphProbe(int AgentTypeId, bool ExteriorSampled, FishWarehouseNativeNavigationVector ExteriorPosition, bool InteriorSampled, bool PathComplete); public readonly record struct FishWarehouseNativeNavigationPathObservation(bool StartSampled, bool EndSampled, FishWarehouseNativeNavigationPathStatus PathStatus); public sealed record FishWarehouseNativeNavigationWorldDescriptor(FishWarehouseNativeNavigationSurface Surface, FishWarehouseNativeNavigationBox BakeBounds, FishWarehouseNativeNavigationLink Link, FishWarehouseNativeNavigationVector DoorwayInterior, float SampleRadius); public sealed record FishWarehouseNativeNavigationBuildRequest(FishWarehouseNativeNavigationWorldDescriptor World, IReadOnlyList IdlePoints, FishWarehouseNativeNavigationVector? LockerPoint, FishWarehouseNativeNavigationVector? PackagingStationPoint, int? PreferredEmployeeAgentTypeId = null); public interface IFishWarehouseNativeNavigationAdapter { IReadOnlyList GetAgentSettings(); IReadOnlyList FindLiveEmployeeAgents(FishWarehouseNativeNavigationEmployeeScope scope); FishWarehouseNativeNavigationRuntimeGraphProbe ProbeGraph(int agentTypeId, FishWarehouseNativeNavigationWorldDescriptor world); int AddSyntheticSurface(int agentTypeId, FishWarehouseNativeNavigationWorldDescriptor world); int AddLink(int agentTypeId, FishWarehouseNativeNavigationWorldDescriptor world, FishWarehouseNativeNavigationVector exteriorEndpoint); void ClearNativeSampleCache(); FishWarehouseNativeNavigationPathObservation ValidatePath(FishWarehouseNativeNavigationGraphRole graphRole, int agentTypeId, string targetName, FishWarehouseNativeNavigationVector start, FishWarehouseNativeNavigationVector end); void Remove(FishWarehouseNativeNavigationTeardownOperation operation); } public sealed record FishWarehouseRestoredObjectObservation(bool IsResolved, string? ParentPropertyCode, bool IsEmployeeHome); public interface IFishWarehouseNativePropertyReplayAdapter { bool IsGuidRegistered(string guid); bool TryGetObjectLoaderLoadOrder(string dataType, out int loadOrder); void LoadObject(int payloadIndex); FishWarehouseRestoredObjectObservation ObserveObject(string guid); } public static class LocalPressureHostIdentity { public static bool TryGetCanonicalHostIdentity(string? activeSaveFolder, out string? identity) { identity = null; if (string.IsNullOrWhiteSpace(activeSaveFolder)) { return false; } string[] array = activeSaveFolder.Trim().TrimEnd('\\', '/').Split(new char[2] { '\\', '/' }, StringSplitOptions.RemoveEmptyEntries); if (array.Length < 3) { return false; } string text = array[^1]; string text2 = array[^2]; string a = array[^3]; if (!text.StartsWith("SaveGame_", StringComparison.OrdinalIgnoreCase) || text.Length == "SaveGame_".Length || !string.Equals(a, "Saves", StringComparison.OrdinalIgnoreCase) || !IsSteamIdentity(text2)) { return false; } identity = text2; return true; } public static bool IsPlaceholderPlayerCode(string? playerCode) { string text = playerCode?.Trim(); if (!string.IsNullOrWhiteSpace(text)) { return string.Equals(text, "0", StringComparison.Ordinal); } return true; } public static bool IsCanonicalPlayerIdentity(string? identity) { if (!string.IsNullOrWhiteSpace(identity)) { return IsSteamIdentity(identity.Trim()); } return false; } private static bool IsSteamIdentity(string identity) { if (identity.Length != 17) { return false; } foreach (char c in identity) { if (c < '0' || c > '9') { return false; } } return true; } } public sealed class LocalPressureHostLifecycleAdapter : ILocalPressureRuntimeHostAdapter, IDisposable { private readonly Action _log; private readonly LocalPressureHostLifecycleBindingState _timeBindingState = new LocalPressureHostLifecycleBindingState(); private TimeManager? _timeManager; private Action? _hourPassHandler; private Action? _sleepEndHandler; private bool _lifecycleSubscribed; private bool _timeEventsSubscribed; private bool _timeBindingDiagnosticIssued; private bool _sleepReadDiagnosticIssued; private long? _lastForwardedSleepEndTotalGameMinutes; private LocalPressureClockBoundary? _forwardingBoundary; private bool _disposed; public string? ActiveSaveFolder { get { try { LoadManager instance = Singleton.Instance; return (instance != null) ? instance.LoadedGameFolderPath : null; } catch { return null; } } } public string? CanonicalHostIdentity { get { if (!LocalPressureHostLifecyclePolicies.TryGetCanonicalHostIdentity(ActiveSaveFolder, out string identity)) { return null; } return identity; } } public event Action? PreLoad; public event Action? LoadComplete; public event Action? SaveStart; public event Action? SaveComplete; public event Action? ClockBoundary; public LocalPressureHostLifecycleAdapter(Action? log = null) { _log = log ?? ((Action)delegate(string message) { MelonLogger.Warning("[Organized Crime] " + message); }); SubscribeLifecycleEvents(); } public LocalPressureHostAuthorityReadStatus ReadHostAuthority() { try { ServerManager serverManager = InstanceFinder.ServerManager; ClientManager clientManager = InstanceFinder.ClientManager; if (serverManager == null || clientManager == null) { return LocalPressureHostAuthorityReadStatus.Pending; } bool flag = serverManager.OneServerStarted(); bool started = clientManager.Started; if (!flag && !started) { return LocalPressureHostAuthorityReadStatus.Pending; } if (!flag) { return LocalPressureHostAuthorityReadStatus.NotAuthoritative; } if (!started) { return LocalPressureHostAuthorityReadStatus.Pending; } return LocalPressureHostAuthorityReadStatus.Ready; } catch { return LocalPressureHostAuthorityReadStatus.Faulted; } } public LocalPressureClockReadStatus TryReadHostClock(out LocalPressureClockSample sample) { //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_0024: 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) sample = default(LocalPressureClockSample); try { TimeManager instance = NetworkSingleton.Instance; if (instance == null) { return LocalPressureClockReadStatus.Pending; } GameDateTime dateTime = instance.GetDateTime(); sample = new LocalPressureClockSample(((GameDateTime)(ref dateTime)).GetMinSum(), dateTime.elapsedDays, dateTime.time, null, _forwardingBoundary.GetValueOrDefault(), DateTime.UtcNow); return LocalPressureClockReadStatus.Ready; } catch { sample = default(LocalPressureClockSample); return LocalPressureClockReadStatus.Faulted; } } public LocalPressurePlayerReadStatus TryReadSupportedPlayers(out IReadOnlyList players) { players = Array.Empty(); try { List playerList = Player.PlayerList; if (playerList == null) { return LocalPressurePlayerReadStatus.Pending; } List list = new List(); Enumerator enumerator = playerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if (current != null && ((NetworkBehaviour)current).IsServerInitialized) { list.Add(new LocalPressurePlayerSample(current.PlayerCode?.Trim(), ((NetworkBehaviour)current).IsServerInitialized, current.IsLocalPlayer, current.PlayerCode?.Trim(), null, null, current, current.Connection != null)); } } return LocalPressureHostLifecyclePolicies.ClassifyPlayerRegistrySnapshot(list, out players); } catch { players = Array.Empty(); return LocalPressurePlayerReadStatus.Faulted; } } public void Dispose() { if (!_disposed) { _disposed = true; UnsubscribeTimeEvents(); if (_lifecycleSubscribed) { GameLifecycle.OnPreLoad -= HandlePreLoad; GameLifecycle.OnLoadComplete -= HandleLoadComplete; GameLifecycle.OnSaveStart -= HandleSaveStart; GameLifecycle.OnSaveComplete -= HandleSaveComplete; GameLifecycle.OnPreSceneChange -= HandlePreSceneChange; _lifecycleSubscribed = false; } this.PreLoad = null; this.LoadComplete = null; this.SaveStart = null; this.SaveComplete = null; this.ClockBoundary = null; } } private void SubscribeLifecycleEvents() { GameLifecycle.OnPreLoad += HandlePreLoad; GameLifecycle.OnLoadComplete += HandleLoadComplete; GameLifecycle.OnSaveStart += HandleSaveStart; GameLifecycle.OnSaveComplete += HandleSaveComplete; GameLifecycle.OnPreSceneChange += HandlePreSceneChange; _lifecycleSubscribed = true; } private void HandlePreLoad() { UnsubscribeTimeEvents(); _timeBindingDiagnosticIssued = false; _sleepReadDiagnosticIssued = false; _lastForwardedSleepEndTotalGameMinutes = null; this.PreLoad?.Invoke(); } private void HandleLoadComplete() { EnsureClockBoundarySubscriptions(); this.LoadComplete?.Invoke(); } private void HandleSaveStart() { this.SaveStart?.Invoke(); } private void HandleSaveComplete() { this.SaveComplete?.Invoke(); } private void HandlePreSceneChange() { UnsubscribeTimeEvents(); _timeBindingDiagnosticIssued = false; _sleepReadDiagnosticIssued = false; _lastForwardedSleepEndTotalGameMinutes = null; } private void HandleHourPass() { if (TryReadSleepInProgress(out var sleepInProgress) && LocalPressureHostLifecyclePolicies.ShouldForwardAwakeHour(sleepInProgress)) { ForwardClockBoundary(LocalPressureClockBoundary.Hour); } } private void HandleSleepEnd() { if (TryReadHostClock(out var sample) == LocalPressureClockReadStatus.Ready && LocalPressureHostLifecyclePolicies.TryAcceptSleepEnd(sample.TotalGameMinutes, ref _lastForwardedSleepEndTotalGameMinutes)) { ForwardClockBoundary(LocalPressureClockBoundary.SleepEnd); } } private bool TryReadSleepInProgress(out bool sleepInProgress) { sleepInProgress = false; try { TimeManager timeManager = _timeManager; if (timeManager == null) { return false; } sleepInProgress = timeManager.IsSleepInProgress; return true; } catch (Exception ex) { if (!_sleepReadDiagnosticIssued) { _sleepReadDiagnosticIssued = true; _log("Local Pressure sleep-state read failed; clock boundary suppressed: " + ex.GetType().Name + ": " + ex.Message); } return false; } } private void ForwardClockBoundary(LocalPressureClockBoundary boundary) { if (_disposed) { return; } _forwardingBoundary = boundary; try { this.ClockBoundary?.Invoke(boundary); } catch (Exception ex) { _log("Local Pressure clock boundary forwarding failed: " + ex.GetType().Name + ": " + ex.Message); } finally { _forwardingBoundary = null; } } public LocalPressureClockBoundaryBindingStatus EnsureClockBoundarySubscriptions() { if (_disposed) { return LocalPressureClockBoundaryBindingStatus.Faulted; } try { TimeManager instance = NetworkSingleton.Instance; if (instance == null) { ReportTimeBindingDiagnostic("TimeManager unavailable; time-event binding deferred to the pre-Active readiness pump."); return LocalPressureClockBoundaryBindingStatus.Pending; } if (_timeEventsSubscribed && _timeBindingState.IsBoundTo(instance)) { return LocalPressureClockBoundaryBindingStatus.Ready; } if (_timeEventsSubscribed) { UnsubscribeTimeEvents(); } _timeManager = instance; _hourPassHandler = DelegateSupport.ConvertDelegate((Delegate)new Action(HandleHourPass)); _sleepEndHandler = DelegateSupport.ConvertDelegate((Delegate)new Action(HandleSleepEnd)); _timeEventsSubscribed = true; TimeManager? timeManager = _timeManager; timeManager.onHourPass += _hourPassHandler; TimeManager? timeManager2 = _timeManager; timeManager2.onSleepEnd += _sleepEndHandler; _timeBindingState.MarkBound(instance); return LocalPressureClockBoundaryBindingStatus.Ready; } catch (Exception ex) { UnsubscribeTimeEvents(); _log("Local Pressure TimeManager event binding failed: " + ex.GetType().Name + ": " + ex.Message); return LocalPressureClockBoundaryBindingStatus.Faulted; } } private void ReportTimeBindingDiagnostic(string message) { if (!_timeBindingDiagnosticIssued) { _timeBindingDiagnosticIssued = true; _log("Local Pressure " + message); } } private void UnsubscribeTimeEvents() { TimeManager timeManager = _timeManager; if (_timeEventsSubscribed && timeManager != null) { try { if (_hourPassHandler != null) { timeManager.onHourPass -= _hourPassHandler; } } catch (Exception ex) { _log("Local Pressure hour-event unsubscription failed: " + ex.GetType().Name + ": " + ex.Message); } try { if (_sleepEndHandler != null) { timeManager.onSleepEnd -= _sleepEndHandler; } } catch (Exception ex2) { _log("Local Pressure sleep-event unsubscription failed: " + ex2.GetType().Name + ": " + ex2.Message); } } _timeManager = null; _hourPassHandler = null; _sleepEndHandler = null; _timeEventsSubscribed = false; _timeBindingState.Clear(); } } public static class LocalPressureHostLifecyclePolicies { public static bool IsAuthoritativeHost(bool isServer, bool isLocalClient) { return isServer && isLocalClient; } public static bool ShouldForwardAwakeHour(bool sleepInProgress) { return !sleepInProgress; } public static bool ShouldForwardDayPass() { return false; } public static bool TryGetCanonicalHostIdentity(string? activeSaveFolder, out string? identity) { return LocalPressureHostIdentity.TryGetCanonicalHostIdentity(activeSaveFolder, out identity); } public static LocalPressurePlayerReadStatus ClassifyPlayerRegistrySnapshot(IReadOnlyList? registryPlayers, out IReadOnlyList supportedPlayers) { supportedPlayers = Array.Empty(); if (registryPlayers == null) { return LocalPressurePlayerReadStatus.Pending; } LocalPressurePlayerSample[] array = registryPlayers.Where((LocalPressurePlayerSample player) => player.IsHostOwned).ToArray(); if (array.Length == 0) { return LocalPressurePlayerReadStatus.Pending; } supportedPlayers = array; if (array.Length > 1) { return LocalPressurePlayerReadStatus.UnsupportedMultiplayer; } LocalPressurePlayerSample localPressurePlayerSample = array[0]; if (!localPressurePlayerSample.HasConnection || IsPlaceholderPlayerCode(localPressurePlayerSample.PlayerCode)) { return LocalPressurePlayerReadStatus.Pending; } return LocalPressurePlayerReadStatus.Ready; } public static bool IsPlaceholderPlayerCode(string? playerCode) { return LocalPressureHostIdentity.IsPlaceholderPlayerCode(playerCode); } public static bool IsCanonicalPlayerIdentity(string? identity) { return LocalPressureHostIdentity.IsCanonicalPlayerIdentity(identity); } public static bool TryAcceptSleepEnd(long totalGameMinutes, ref long? lastForwardedTotalGameMinutes) { if (lastForwardedTotalGameMinutes == totalGameMinutes) { return false; } lastForwardedTotalGameMinutes = totalGameMinutes; return true; } } public sealed class LocalPressureHostLifecycleBindingState { private object? _boundInstance; public bool IsBoundTo(object instance) { return _boundInstance == instance; } public void MarkBound(object instance) { _boundInstance = instance ?? throw new ArgumentNullException("instance"); } public void Clear() { _boundInstance = null; } } public interface ILocalPressureRuntimeCensusSource { bool TryReadSnapshot(out LocalPressureRuntimeCensusSnapshot snapshot); } public readonly record struct LocalPressureRuntimeCensusSnapshot(bool IsServer, bool IsHost, long? TotalGameMinutes, int? ElapsedDays, int? Time24h, double? CurrentTime, bool IsSleepInProgress, bool SaveFolderPresent, int PlayerCount, bool HostOwnedPlayerCodePresent, bool HostOwnedPlayerCodeUnique, bool ConnectionPresent); public readonly record struct LocalPressureRuntimeCensusRow(int Sequence, string CallbackName, DateTime ReceivedAtUtc, bool SnapshotAvailable, LocalPressureRuntimeCensusSnapshot Snapshot) { public long? TotalGameMinutes { get { if (!SnapshotAvailable) { return null; } return Snapshot.TotalGameMinutes; } } public int? ElapsedDays { get { if (!SnapshotAvailable) { return null; } return Snapshot.ElapsedDays; } } public int? Time24h { get { if (!SnapshotAvailable) { return null; } return Snapshot.Time24h; } } public double? CurrentTime { get { if (!SnapshotAvailable) { return null; } return Snapshot.CurrentTime; } } public bool SaveFolderPresent { get { if (SnapshotAvailable) { return Snapshot.SaveFolderPresent; } return false; } } public int? PlayerCount { get { if (!SnapshotAvailable) { return null; } return Snapshot.PlayerCount; } } public bool HostOwnedPlayerCodePresent { get { if (SnapshotAvailable) { return Snapshot.HostOwnedPlayerCodePresent; } return false; } } public bool HostOwnedPlayerCodeUnique { get { if (SnapshotAvailable) { return Snapshot.HostOwnedPlayerCodeUnique; } return false; } } public bool ConnectionPresent { get { if (SnapshotAvailable) { return Snapshot.ConnectionPresent; } return false; } } } public sealed class LocalPressureRuntimeCensusObserver : IDisposable { private readonly int _maxRows; private readonly List _rows = new List(); private bool _disposed; public IReadOnlyList Rows => _rows; public bool CapacityReached => _rows.Count >= _maxRows; public LocalPressureRuntimeCensusObserver(int maxRows = 256) { if (maxRows <= 0) { throw new ArgumentOutOfRangeException("maxRows"); } _maxRows = maxRows; } public void Observe(string callbackName, ILocalPressureRuntimeCensusSource source) { if (!_disposed && _rows.Count < _maxRows) { string callbackName2 = (string.IsNullOrWhiteSpace(callbackName) ? "(unknown)" : callbackName.Trim()); bool flag = false; LocalPressureRuntimeCensusSnapshot snapshot = default(LocalPressureRuntimeCensusSnapshot); try { flag = source?.TryReadSnapshot(out snapshot) ?? false; } catch { flag = false; snapshot = default(LocalPressureRuntimeCensusSnapshot); } _rows.Add(new LocalPressureRuntimeCensusRow(_rows.Count + 1, callbackName2, DateTime.UtcNow, flag, snapshot)); } } public void Dispose() { if (!_disposed) { _disposed = true; _rows.Clear(); } } } public sealed class LocalPressureRuntimeComposition : IDisposable { private bool _disposed; public ILocalPressureRuntimeHostAdapter Adapter { get; } public LocalPressureRuntimeService Service { get; } public LocalPressureRuntimeComposition(ILocalPressureRuntimeHostAdapter adapter, Func repositoryFactory, Action? log = null, ILocalPressureTierTransitionSink? tierTransitionSink = null, Action? receiptLog = null) { Adapter = adapter ?? throw new ArgumentNullException("adapter"); Service = new LocalPressureRuntimeService(adapter, repositoryFactory, null, null, log, null, tierTransitionSink, receiptLog); } public void Dispose() { if (!_disposed) { _disposed = true; Service.Dispose(); } } } public enum LocalPressureRuntimePhase { Detached, AwaitingLoad, AwaitingHostBaseline, Active, Quarantined, Saving, Disposed } public enum LocalPressureClockBoundary { HostReady, Hour, Day, SleepEnd } public enum LocalPressureRuntimeRejectReason { None, HostNotReady, NotAuthoritativeHost, MissingClock, InvalidClock, StaleSequence, DuplicateSample, ClockRewound, WrongEpoch, LifecycleTransition, IdentityUnavailable, IdentityAmbiguous, UnsupportedMultiplayer, SavePathUnavailable, SidecarLoadFailed, SidecarSaveFailed, AdapterFailure, Disposed } public readonly record struct LocalPressureClockSample(long TotalGameMinutes, int ElapsedDays, int Time24h, uint? SourceSequence, LocalPressureClockBoundary Boundary, DateTime ReceivedAtUtc); public readonly record struct LocalPressurePlayerSample(string? PlayerId, bool IsHostOwned, bool IsLocalPlayer, string? PlayerCode, string? Region, string? PropertyCode, object? SourcePlayer = null, bool HasConnection = true); public enum LocalPressureHostAuthorityReadStatus { Ready, Pending, NotAuthoritative, Faulted } public enum LocalPressurePlayerReadStatus { Ready, Pending, UnsupportedMultiplayer, Faulted } public enum LocalPressureClockReadStatus { Ready, Pending, Faulted } public enum LocalPressureClockBoundaryBindingStatus { Ready, Pending, Faulted } public delegate LocalPressureDecayResult LocalPressureDecayEvaluator(LocalPressureDecayInput input, LocalPressureProfile profile); public interface ILocalPressureRuntimeHostAdapter : IDisposable { string? ActiveSaveFolder { get; } string? CanonicalHostIdentity { get; } event Action? PreLoad; event Action? LoadComplete; event Action? SaveStart; event Action? SaveComplete; event Action? ClockBoundary; LocalPressureHostAuthorityReadStatus ReadHostAuthority(); LocalPressureClockBoundaryBindingStatus EnsureClockBoundarySubscriptions(); LocalPressureClockReadStatus TryReadHostClock(out LocalPressureClockSample sample); LocalPressurePlayerReadStatus TryReadSupportedPlayers(out IReadOnlyList players); } public interface ILocalPressureStateRepository { LocalPressureStoreLoadResult Load(); LocalPressureStoreUpdateResult Update(LocalPressurePlayerRecord record); } public readonly record struct LocalPressureActiveEvidenceSnapshot(Guid SessionEpoch, long LoadEpoch, string PlayerId, object? SupportedPlayer); public sealed record CustodyEntryEvidence(string PlayerId, string CorrelationId, string? Region, string? PropertyCode); public enum LocalPressureEvidenceWriteRejectReason { None, RuntimeNotActive, NotAuthoritativeHost, MissingIdentity, PlayerMismatch, StaleEpoch, MissingCorrelation, InvalidCorrelation, DuplicateCorrelation, ClockUnavailable, InvalidClock, UnsupportedMultiplayer, AdapterFailure, Disposed } public sealed record LocalPressureEvidenceWriteResult(bool Accepted, LocalPressureEvidenceWriteRejectReason RejectReason, LocalPressureState? State, string Message); public delegate LocalPressureTransitionResult LocalPressureEvidenceEvaluator(LocalPressureState state, LocalPressureEvidenceEvent evidence, LocalPressureProfile profile); public interface ILocalPressureCustodyEvidenceWriter { bool TryGetActiveEvidenceSnapshot(out LocalPressureActiveEvidenceSnapshot snapshot); LocalPressureEvidenceWriteResult TryApplyCustodyEvidence(CustodyEntryEvidence evidence, Guid sessionEpoch, long loadEpoch); } public sealed class LocalPressureRuntimeService : IDisposable, ILocalPressureCustodyEvidenceWriter { private readonly ILocalPressureRuntimeHostAdapter _adapter; private readonly Func _repositoryFactory; private readonly LocalPressureProfile _profile; private readonly LocalPressureDecayEvaluator _decayEvaluator; private readonly LocalPressureEvidenceEvaluator _evidenceEvaluator; private readonly ILocalPressureTierTransitionSink? _tierTransitionSink; private readonly Action _log; private readonly Action _receiptLog; private readonly object _evidenceSync = new object(); private readonly Dictionary _states = new Dictionary(StringComparer.Ordinal); private readonly HashSet _acceptedEvidenceCorrelations = new HashSet(StringComparer.Ordinal); private ILocalPressureStateRepository? _repository; private LocalPressureRuntimePhase _phase; private LocalPressureRuntimePhase? _phaseBeforeSave; private Dictionary? _saveSnapshot; private bool _hasAcceptedClock; private bool _loadRetryPending; private long _lastAcceptedTotalGameMinutes; private uint? _lastAcceptedSequence; private bool _disposed; public Guid SessionEpoch { get; } public long LoadEpoch { get; private set; } public LocalPressureRuntimePhase Phase => _phase; public LocalPressureRuntimeRejectReason LastRejectReason { get; private set; } public long? AcceptedClockTotalGameMinutes { get { if (!_hasAcceptedClock) { return null; } return _lastAcceptedTotalGameMinutes; } } public LocalPressureRuntimeService(ILocalPressureRuntimeHostAdapter adapter, Func repositoryFactory, LocalPressureProfile? profile = null, LocalPressureDecayEvaluator? decayEvaluator = null, Action? log = null, LocalPressureEvidenceEvaluator? evidenceEvaluator = null, ILocalPressureTierTransitionSink? tierTransitionSink = null, Action? receiptLog = null) { _adapter = adapter ?? throw new ArgumentNullException("adapter"); _repositoryFactory = repositoryFactory ?? throw new ArgumentNullException("repositoryFactory"); _profile = profile ?? LocalPressureProfile.Moderate; _decayEvaluator = decayEvaluator ?? new LocalPressureDecayEvaluator(LocalPressureDecay.Evaluate); _evidenceEvaluator = evidenceEvaluator ?? new LocalPressureEvidenceEvaluator(LocalPressureTransitions.ApplyEvidence); _tierTransitionSink = tierTransitionSink; _log = log ?? ((Action)delegate { }); _receiptLog = receiptLog ?? _log; SessionEpoch = Guid.NewGuid(); _adapter.PreLoad += OnPreLoad; _adapter.LoadComplete += OnLoadComplete; _adapter.SaveStart += OnSaveStart; _adapter.SaveComplete += OnSaveComplete; _adapter.ClockBoundary += OnClockBoundary; } public bool TryGetState(string playerId, out LocalPressureState? state) { if (string.IsNullOrWhiteSpace(playerId)) { state = null; return false; } return _states.TryGetValue(playerId, out state); } public bool TryGetActiveEvidenceSnapshot(out LocalPressureActiveEvidenceSnapshot snapshot) { lock (_evidenceSync) { snapshot = default(LocalPressureActiveEvidenceSnapshot); if (_disposed || _phase != LocalPressureRuntimePhase.Active) { return false; } LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); if (localPressureHostAuthorityReadStatus != LocalPressureHostAuthorityReadStatus.Ready) { switch (localPressureHostAuthorityReadStatus) { case LocalPressureHostAuthorityReadStatus.Pending: Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: true); break; case LocalPressureHostAuthorityReadStatus.NotAuthoritative: Reject(LocalPressureRuntimeRejectReason.NotAuthoritativeHost, quarantine: true); break; } return false; } if (!TryResolveSinglePlayer(out var player) || string.IsNullOrWhiteSpace(player.PlayerId)) { return false; } snapshot = new LocalPressureActiveEvidenceSnapshot(SessionEpoch, LoadEpoch, player.PlayerId, player.SourcePlayer); return true; } } public LocalPressureEvidenceWriteResult TryApplyCustodyEvidence(CustodyEntryEvidence evidence, Guid sessionEpoch, long loadEpoch) { LocalPressureEvidenceWriteResult result; LocalPressureTierTransitionNotification notification; lock (_evidenceSync) { result = TryApplyCustodyEvidenceLocked(evidence, sessionEpoch, loadEpoch, out notification); } PublishTransition(notification); return result; } public void OnPreLoad() { if (CanHandleLifecycle()) { if (_phase == LocalPressureRuntimePhase.AwaitingLoad) { ClearRejectReason(); return; } LoadEpoch++; ResetTransitionSinkForEpoch(); _repository = null; _loadRetryPending = false; _states.Clear(); _acceptedEvidenceCorrelations.Clear(); ClearAcceptedClock(); _phaseBeforeSave = null; _saveSnapshot = null; SetPhase(LocalPressureRuntimePhase.AwaitingLoad); ClearRejectReason(); } } public void OnLoadComplete() { if (CanHandleLifecycle()) { if (_phase == LocalPressureRuntimePhase.AwaitingHostBaseline || _phase == LocalPressureRuntimePhase.Active || _phase == LocalPressureRuntimePhase.Saving) { ClearRejectReason(); } else if (_phase != LocalPressureRuntimePhase.AwaitingLoad && _phase != LocalPressureRuntimePhase.Detached) { Reject(LocalPressureRuntimeRejectReason.LifecycleTransition, quarantine: false); } else { TryCompleteLoad(); } } } private void TryCompleteLoad() { _loadRetryPending = false; LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); switch (localPressureHostAuthorityReadStatus) { case LocalPressureHostAuthorityReadStatus.Pending: _loadRetryPending = true; Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: false); break; default: Reject((localPressureHostAuthorityReadStatus == LocalPressureHostAuthorityReadStatus.NotAuthoritative) ? LocalPressureRuntimeRejectReason.NotAuthoritativeHost : LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); break; case LocalPressureHostAuthorityReadStatus.Ready: { ILocalPressureStateRepository localPressureStateRepository; try { localPressureStateRepository = _repositoryFactory(_adapter.ActiveSaveFolder); } catch (Exception ex) { _log("Local Pressure repository creation failed: " + ex.Message); _loadRetryPending = false; Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); break; } if (localPressureStateRepository == null) { _loadRetryPending = true; Reject(LocalPressureRuntimeRejectReason.SavePathUnavailable, quarantine: false); break; } LocalPressureStoreLoadResult localPressureStoreLoadResult; try { localPressureStoreLoadResult = localPressureStateRepository.Load(); } catch (Exception ex2) { _log("Local Pressure sidecar load failed: " + ex2.Message); _loadRetryPending = false; Reject(LocalPressureRuntimeRejectReason.SidecarLoadFailed, quarantine: true); break; } if (!localPressureStoreLoadResult.Succeeded || (object)localPressureStoreLoadResult.Envelope == null) { _log("Local Pressure sidecar load was rejected: " + localPressureStoreLoadResult.Message); _loadRetryPending = false; Reject(LocalPressureRuntimeRejectReason.SidecarLoadFailed, quarantine: true); break; } try { Dictionary dictionary = localPressureStoreLoadResult.Envelope.Players.ToDictionary((LocalPressurePlayerRecord record) => record.PlayerId, (LocalPressurePlayerRecord record) => record.ToState(), StringComparer.Ordinal); _states.Clear(); foreach (KeyValuePair item in dictionary) { _states.Add(item.Key, item.Value); } } catch (Exception ex3) { _log("Local Pressure sidecar state hydration failed: " + ex3.Message); _states.Clear(); _loadRetryPending = false; Reject(LocalPressureRuntimeRejectReason.SidecarLoadFailed, quarantine: true); break; } _repository = localPressureStateRepository; _loadRetryPending = false; ClearAcceptedClock(); SetPhase(LocalPressureRuntimePhase.AwaitingHostBaseline); ClearRejectReason(); _receiptLog($"Local Pressure repository hydrated; players={_states.Count}; loadEpoch={LoadEpoch}."); break; } } } public void OnClockBoundary(LocalPressureClockBoundary boundary) { if (!CanHandleLifecycle()) { return; } if (_phase == LocalPressureRuntimePhase.AwaitingHostBaseline) { if (boundary == LocalPressureClockBoundary.HostReady) { TryEstablishBaseline(); } } else { if (_phase != LocalPressureRuntimePhase.Active || (boundary != LocalPressureClockBoundary.Hour && boundary != LocalPressureClockBoundary.SleepEnd)) { return; } LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); switch (localPressureHostAuthorityReadStatus) { case LocalPressureHostAuthorityReadStatus.Pending: Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: true); break; default: Reject((localPressureHostAuthorityReadStatus == LocalPressureHostAuthorityReadStatus.NotAuthoritative) ? LocalPressureRuntimeRejectReason.NotAuthoritativeHost : LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); break; case LocalPressureHostAuthorityReadStatus.Ready: { if (TryReadClock(out var sample) && ValidateClock(sample) && TryResolveSinglePlayer(out var player) && ValidateClockCursor(sample)) { LocalPressureState orCreateContextualState = GetOrCreateContextualState(player); LocalPressureDecayResult localPressureDecayResult; try { localPressureDecayResult = _decayEvaluator(new LocalPressureDecayInput(orCreateContextualState, (double)sample.TotalGameMinutes / 60.0, ActivePursuit: false), _profile); } catch (Exception ex) { _log("Local Pressure decay evaluation failed: " + ex.Message); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); break; } _states[player.PlayerId] = localPressureDecayResult.State; CommitClock(sample); ClearRejectReason(); } break; } } } } public void PumpReadiness() { if (_phase == LocalPressureRuntimePhase.AwaitingLoad && _loadRetryPending) { TryCompleteLoad(); } if (_phase == LocalPressureRuntimePhase.AwaitingHostBaseline) { TryEstablishBaseline(); } } private void TryEstablishBaseline() { if (!CanHandleLifecycle() || _phase != LocalPressureRuntimePhase.AwaitingHostBaseline) { return; } LocalPressureClockBoundaryBindingStatus localPressureClockBoundaryBindingStatus; try { localPressureClockBoundaryBindingStatus = _adapter.EnsureClockBoundarySubscriptions(); } catch (Exception ex) { _log("Local Pressure clock-boundary subscription failed: " + ex.Message); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); return; } switch (localPressureClockBoundaryBindingStatus) { case LocalPressureClockBoundaryBindingStatus.Pending: Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: false); break; default: Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); break; case LocalPressureClockBoundaryBindingStatus.Ready: { LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); switch (localPressureHostAuthorityReadStatus) { case LocalPressureHostAuthorityReadStatus.Pending: Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: false); break; default: Reject((localPressureHostAuthorityReadStatus == LocalPressureHostAuthorityReadStatus.NotAuthoritative) ? LocalPressureRuntimeRejectReason.NotAuthoritativeHost : LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); break; case LocalPressureHostAuthorityReadStatus.Ready: { if (TryReadClock(out var sample) && ValidateClock(sample) && TryResolveSinglePlayer(out var player) && ValidateClockCursor(sample)) { CommitClock(sample); SetPhase(LocalPressureRuntimePhase.Active); ClearRejectReason(); _receiptLog($"Local Pressure runtime Active; playerId={player.PlayerId}; loadEpoch={LoadEpoch}."); } break; } } break; } } } public void OnSaveStart() { if (!CanHandleLifecycle()) { return; } if (_phase == LocalPressureRuntimePhase.Saving) { ClearRejectReason(); return; } if (_phase != LocalPressureRuntimePhase.Active && _phase != LocalPressureRuntimePhase.AwaitingHostBaseline) { Reject(LocalPressureRuntimeRejectReason.LifecycleTransition, quarantine: false); return; } _phaseBeforeSave = _phase; _saveSnapshot = _states.ToDictionary, string, LocalPressureState>((KeyValuePair pair) => pair.Key, (KeyValuePair pair) => pair.Value, StringComparer.Ordinal); SetPhase(LocalPressureRuntimePhase.Saving); ClearRejectReason(); _log($"Local Pressure SaveStart snapshot: players={_saveSnapshot.Count}; loadEpoch={LoadEpoch}."); } public void OnSaveComplete() { if (!CanHandleLifecycle() || _phase != LocalPressureRuntimePhase.Saving) { return; } LocalPressureRuntimePhase localPressureRuntimePhase = _phaseBeforeSave ?? LocalPressureRuntimePhase.AwaitingLoad; _phaseBeforeSave = null; Dictionary states = _saveSnapshot ?? new Dictionary(StringComparer.Ordinal); _saveSnapshot = null; if (localPressureRuntimePhase != LocalPressureRuntimePhase.Active) { SetPhase(localPressureRuntimePhase); ClearRejectReason(); return; } LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); if (localPressureHostAuthorityReadStatus != LocalPressureHostAuthorityReadStatus.Ready) { Reject(localPressureHostAuthorityReadStatus switch { LocalPressureHostAuthorityReadStatus.NotAuthoritative => LocalPressureRuntimeRejectReason.NotAuthoritativeHost, LocalPressureHostAuthorityReadStatus.Pending => LocalPressureRuntimeRejectReason.HostNotReady, _ => LocalPressureRuntimeRejectReason.AdapterFailure, }, quarantine: true); return; } if (_repository == null) { Reject(LocalPressureRuntimeRejectReason.SavePathUnavailable, quarantine: false); SetPhase(LocalPressureRuntimePhase.Active); return; } if (!TryResolveSinglePlayer(out var player)) { SetPhase(LocalPressureRuntimePhase.Quarantined); return; } LocalPressureState orCreateState = GetOrCreateState(player.PlayerId, states); LocalPressurePlayerRecord record = LocalPressurePlayerRecord.FromState(orCreateState); LocalPressureStoreUpdateResult localPressureStoreUpdateResult; try { localPressureStoreUpdateResult = _repository.Update(record); } catch (Exception ex) { _log("Local Pressure sidecar save failed: " + ex.Message); Reject(LocalPressureRuntimeRejectReason.SidecarSaveFailed, quarantine: false); SetPhase(LocalPressureRuntimePhase.Active); return; } if (!localPressureStoreUpdateResult.Succeeded) { _log("Local Pressure sidecar save was rejected: " + localPressureStoreUpdateResult.Message); Reject(LocalPressureRuntimeRejectReason.SidecarSaveFailed, quarantine: false); SetPhase(LocalPressureRuntimePhase.Active); return; } _states[player.PlayerId] = orCreateState; SetPhase(LocalPressureRuntimePhase.Active); ClearRejectReason(); _log($"Local Pressure sidecar write succeeded; playerId={player.PlayerId}; revision={orCreateState.Revision}; loadEpoch={LoadEpoch}."); } public void Dispose() { if (_disposed) { return; } _disposed = true; SetPhase(LocalPressureRuntimePhase.Disposed); _adapter.PreLoad -= OnPreLoad; _adapter.LoadComplete -= OnLoadComplete; _adapter.SaveStart -= OnSaveStart; _adapter.SaveComplete -= OnSaveComplete; _adapter.ClockBoundary -= OnClockBoundary; _repository = null; _loadRetryPending = false; _states.Clear(); _acceptedEvidenceCorrelations.Clear(); ClearAcceptedClock(); _saveSnapshot = null; try { _adapter.Dispose(); } catch (Exception ex) { _log("Local Pressure host adapter disposal failed: " + ex.Message); } } private bool CanHandleLifecycle() { if (!_disposed) { return true; } SetRejectReason(LocalPressureRuntimeRejectReason.Disposed); return false; } private LocalPressureHostAuthorityReadStatus ReadHostAuthority() { try { LocalPressureHostAuthorityReadStatus num = _adapter.ReadHostAuthority(); if (num == LocalPressureHostAuthorityReadStatus.Faulted) { _log("Local Pressure host authority read returned Faulted."); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); } return num; } catch (Exception ex) { _log("Local Pressure host authority read failed: " + ex.Message); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); return LocalPressureHostAuthorityReadStatus.Faulted; } } private bool TryReadClock(out LocalPressureClockSample sample) { try { switch (_adapter.TryReadHostClock(out sample)) { case LocalPressureClockReadStatus.Ready: return true; case LocalPressureClockReadStatus.Faulted: _log("Local Pressure host clock read returned Faulted."); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); return false; } } catch (Exception ex) { _log("Local Pressure host clock read failed: " + ex.Message); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); sample = default(LocalPressureClockSample); return false; } sample = default(LocalPressureClockSample); Reject(LocalPressureRuntimeRejectReason.MissingClock, quarantine: false); return false; } private bool ValidateClock(LocalPressureClockSample sample) { if (sample.TotalGameMinutes < 0 || sample.ElapsedDays < 0) { if (LastRejectReason != LocalPressureRuntimeRejectReason.InvalidClock) { _log($"Local Pressure invalid clock sample: totalGameMinutes={sample.TotalGameMinutes}; elapsedDays={sample.ElapsedDays}; time24h={sample.Time24h}; loadEpoch={LoadEpoch}."); } Reject(LocalPressureRuntimeRejectReason.InvalidClock, quarantine: true); return false; } return true; } private bool ValidateClockCursor(LocalPressureClockSample sample) { if (!_hasAcceptedClock) { return true; } if (sample.TotalGameMinutes < _lastAcceptedTotalGameMinutes) { Reject(LocalPressureRuntimeRejectReason.ClockRewound, quarantine: true); return false; } if (sample.TotalGameMinutes == _lastAcceptedTotalGameMinutes) { Reject(LocalPressureRuntimeRejectReason.DuplicateSample, quarantine: false); return false; } if (sample.SourceSequence.HasValue && _lastAcceptedSequence.HasValue && sample.SourceSequence.Value <= _lastAcceptedSequence.Value) { Reject(LocalPressureRuntimeRejectReason.StaleSequence, quarantine: false); return false; } return true; } private void CommitClock(LocalPressureClockSample sample) { _hasAcceptedClock = true; _lastAcceptedTotalGameMinutes = sample.TotalGameMinutes; _lastAcceptedSequence = sample.SourceSequence; } private bool TryResolveSinglePlayer(out LocalPressurePlayerSample player) { player = default(LocalPressurePlayerSample); IReadOnlyList players; try { switch (_adapter.TryReadSupportedPlayers(out players)) { case LocalPressurePlayerReadStatus.Pending: Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, _phase == LocalPressureRuntimePhase.Active); return false; case LocalPressurePlayerReadStatus.UnsupportedMultiplayer: Reject(LocalPressureRuntimeRejectReason.UnsupportedMultiplayer, quarantine: true); return false; case LocalPressurePlayerReadStatus.Faulted: _log("Local Pressure player identity read returned Faulted."); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); return false; default: if (players == null) { Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, _phase == LocalPressureRuntimePhase.Active); return false; } break; } } catch (Exception ex) { _log("Local Pressure player identity read failed: " + ex.Message); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); return false; } if (players.Count != 1) { Reject((players.Count > 1) ? LocalPressureRuntimeRejectReason.UnsupportedMultiplayer : LocalPressureRuntimeRejectReason.IdentityUnavailable, players.Count > 1 || _phase == LocalPressureRuntimePhase.Active); return false; } player = players[0]; if (!player.IsHostOwned) { Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, _phase == LocalPressureRuntimePhase.Active); return false; } string text; try { text = _adapter.CanonicalHostIdentity?.Trim(); } catch (Exception ex2) { _log("Local Pressure canonical identity read failed: " + ex2.Message); Reject(LocalPressureRuntimeRejectReason.AdapterFailure, quarantine: true); return false; } if (string.IsNullOrWhiteSpace(text) || text.Contains('\0')) { Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, _phase == LocalPressureRuntimePhase.Active); return false; } string text2 = player.PlayerCode?.Trim(); if (LocalPressureHostLifecyclePolicies.IsPlaceholderPlayerCode(text2)) { Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, _phase == LocalPressureRuntimePhase.Active); return false; } if (!player.HasConnection) { Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, _phase == LocalPressureRuntimePhase.Active); return false; } if (text2 != null && text2.Contains('\0')) { Reject(LocalPressureRuntimeRejectReason.IdentityUnavailable, quarantine: true); return false; } if (LocalPressureHostLifecyclePolicies.IsCanonicalPlayerIdentity(text2) && !string.Equals(text, text2, StringComparison.Ordinal)) { Reject(LocalPressureRuntimeRejectReason.IdentityAmbiguous, quarantine: true); return false; } player = player with { PlayerId = text, PlayerCode = text2 }; return true; } private LocalPressureState GetOrCreateContextualState(LocalPressurePlayerSample player) { string playerId = player.PlayerId; if (!_states.TryGetValue(playerId, out LocalPressureState value)) { value = LocalPressureState.Quiet(playerId); } return new LocalPressureState(value.LocalHeat, value.KnownOffender, value.LastEvidenceGameTime, value.QuietGraceUntil, value.LastDecayEvaluation, playerId, player.Region ?? value.Region, player.PropertyCode ?? value.PropertyCode, value.Revision); } private LocalPressureState GetOrCreateState(string playerId, IReadOnlyDictionary? states = null) { if ((states ?? _states).TryGetValue(playerId, out LocalPressureState value)) { return value; } return LocalPressureState.Quiet(playerId); } private void ClearAcceptedClock() { _hasAcceptedClock = false; _lastAcceptedTotalGameMinutes = 0L; _lastAcceptedSequence = null; } private LocalPressureEvidenceWriteResult MapIdentityReject() { return EvidenceReject(LastRejectReason switch { LocalPressureRuntimeRejectReason.UnsupportedMultiplayer => LocalPressureEvidenceWriteRejectReason.UnsupportedMultiplayer, LocalPressureRuntimeRejectReason.IdentityAmbiguous => LocalPressureEvidenceWriteRejectReason.PlayerMismatch, LocalPressureRuntimeRejectReason.IdentityUnavailable => LocalPressureEvidenceWriteRejectReason.MissingIdentity, _ => LocalPressureEvidenceWriteRejectReason.AdapterFailure, }, "Supported host player identity was unavailable."); } private static bool TryValidateCorrelation(string correlationId, Guid sessionEpoch, long loadEpoch, string playerId) { string[] array = correlationId.Split('/'); if (array.Length == 6 && string.Equals(array[0], "custody", StringComparison.Ordinal) && string.Equals(array[1], "v1", StringComparison.Ordinal) && Guid.TryParse(array[2], out var result) && result == sessionEpoch && long.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) && result2 == loadEpoch && string.Equals(array[4], playerId.Trim(), StringComparison.Ordinal) && long.TryParse(array[5], NumberStyles.None, CultureInfo.InvariantCulture, out var result3)) { return result3 > 0; } return false; } private static (string? Region, string? PropertyCode) NormalizeEvidenceContext(string? region, string? propertyCode) { region = NormalizeContextValue(region); propertyCode = NormalizeContextValue(propertyCode); if (region != null && propertyCode != null) { return (Region: region, PropertyCode: propertyCode); } return (Region: null, PropertyCode: null); } private static string? NormalizeContextValue(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } string text = value.Trim(); if (text.Length > 128 || text.Any((char character) => char.IsControl(character))) { return null; } return text; } private static LocalPressureEvidenceWriteResult EvidenceReject(LocalPressureEvidenceWriteRejectReason reason, string message) { return new LocalPressureEvidenceWriteResult(Accepted: false, reason, null, message); } private LocalPressureEvidenceWriteResult TryApplyCustodyEvidenceLocked(CustodyEntryEvidence evidence, Guid sessionEpoch, long loadEpoch, out LocalPressureTierTransitionNotification? notification) { notification = null; if (_disposed) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.Disposed, "Local Pressure runtime was disposed."); } if (_phase != LocalPressureRuntimePhase.Active) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.RuntimeNotActive, "Local Pressure runtime was not Active."); } if (sessionEpoch != SessionEpoch || loadEpoch != LoadEpoch) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.StaleEpoch, "Custody evidence epoch was stale."); } LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); if (localPressureHostAuthorityReadStatus != LocalPressureHostAuthorityReadStatus.Ready) { switch (localPressureHostAuthorityReadStatus) { case LocalPressureHostAuthorityReadStatus.Pending: Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: true); break; case LocalPressureHostAuthorityReadStatus.NotAuthoritative: Reject(LocalPressureRuntimeRejectReason.NotAuthoritativeHost, quarantine: true); break; } if (localPressureHostAuthorityReadStatus != LocalPressureHostAuthorityReadStatus.Faulted) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.NotAuthoritativeHost, "Custody evidence was not host-authoritative."); } return EvidenceReject(LocalPressureEvidenceWriteRejectReason.AdapterFailure, "Custody evidence host-authority read failed."); } if ((object)evidence == null || string.IsNullOrWhiteSpace(evidence.PlayerId) || evidence.PlayerId.Contains('\0')) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.MissingIdentity, "Custody evidence player identity was unavailable."); } if (!TryResolveSinglePlayer(out var player) || string.IsNullOrWhiteSpace(player.PlayerId)) { return MapIdentityReject(); } if (!string.Equals(player.PlayerId, evidence.PlayerId.Trim(), StringComparison.Ordinal)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.PlayerMismatch, "Custody evidence player identity did not match the supported host player."); } if (string.IsNullOrWhiteSpace(evidence.CorrelationId)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.MissingCorrelation, "Custody evidence correlation was unavailable."); } if (!TryValidateCorrelation(evidence.CorrelationId, sessionEpoch, loadEpoch, evidence.PlayerId)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.InvalidCorrelation, "Custody evidence correlation was invalid for the current identity or epoch."); } if (_acceptedEvidenceCorrelations.Contains(evidence.CorrelationId)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.DuplicateCorrelation, "Custody evidence correlation was already accepted."); } if (!_hasAcceptedClock) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.ClockUnavailable, "Local Pressure host clock baseline was unavailable."); } if (!TryReadClock(out var sample)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.ClockUnavailable, "Local Pressure host clock could not be read."); } if (!ValidateClock(sample) || sample.TotalGameMinutes < _lastAcceptedTotalGameMinutes || (sample.SourceSequence.HasValue && _lastAcceptedSequence.HasValue && sample.SourceSequence.Value <= _lastAcceptedSequence.Value)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.InvalidClock, "Custody evidence host clock was invalid or stale."); } string text = evidence.PlayerId.Trim(); LocalPressureState orCreateState = GetOrCreateState(text); (string? Region, string? PropertyCode) tuple = NormalizeEvidenceContext(evidence.Region, evidence.PropertyCode); string region = tuple.Region; string propertyCode = tuple.PropertyCode; double num = (double)sample.TotalGameMinutes / 60.0; if (orCreateState.LastEvidenceGameTime.HasValue && orCreateState.LastEvidenceGameTime.Value > num) { region = null; propertyCode = null; } LocalPressureEvidenceEvent evidence2 = new LocalPressureEvidenceEvent(LocalPressureReasonCode.Arrest, num, text, region, propertyCode, evidence.CorrelationId, num); LocalPressureTransitionResult localPressureTransitionResult; try { localPressureTransitionResult = _evidenceEvaluator(orCreateState, evidence2, _profile); } catch (Exception ex) { _log("Local Pressure custody evidence transition failed: " + ex.Message); return EvidenceReject(LocalPressureEvidenceWriteRejectReason.AdapterFailure, "Custody evidence transition failed."); } _states[text] = localPressureTransitionResult.State; _acceptedEvidenceCorrelations.Add(evidence.CorrelationId); notification = new LocalPressureTierTransitionNotification(SessionEpoch, LoadEpoch, text, player.SourcePlayer, evidence.CorrelationId, localPressureTransitionResult.PreviousTier, localPressureTransitionResult.CurrentTier, region, propertyCode); return new LocalPressureEvidenceWriteResult(Accepted: true, LocalPressureEvidenceWriteRejectReason.None, localPressureTransitionResult.State, "Custody evidence accepted."); } public LocalPressureEvidenceWriteResult TryApplyRecordWipe(string playerId, Guid sessionEpoch, long loadEpoch) { lock (_evidenceSync) { if (_disposed) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.Disposed, "Local Pressure runtime was disposed."); } if (_phase != LocalPressureRuntimePhase.Active) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.RuntimeNotActive, "Local Pressure runtime was not Active."); } if (sessionEpoch != SessionEpoch || loadEpoch != LoadEpoch) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.StaleEpoch, "Record wipe epoch was stale."); } LocalPressureHostAuthorityReadStatus localPressureHostAuthorityReadStatus = ReadHostAuthority(); if (localPressureHostAuthorityReadStatus != LocalPressureHostAuthorityReadStatus.Ready) { switch (localPressureHostAuthorityReadStatus) { case LocalPressureHostAuthorityReadStatus.Pending: Reject(LocalPressureRuntimeRejectReason.HostNotReady, quarantine: true); break; case LocalPressureHostAuthorityReadStatus.NotAuthoritative: Reject(LocalPressureRuntimeRejectReason.NotAuthoritativeHost, quarantine: true); break; } return (localPressureHostAuthorityReadStatus == LocalPressureHostAuthorityReadStatus.Faulted) ? EvidenceReject(LocalPressureEvidenceWriteRejectReason.AdapterFailure, "Record wipe host-authority read failed.") : EvidenceReject(LocalPressureEvidenceWriteRejectReason.NotAuthoritativeHost, "Record wipe was not host-authoritative."); } if (string.IsNullOrWhiteSpace(playerId) || playerId.Contains('\0')) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.MissingIdentity, "Record wipe player identity was unavailable."); } if (!TryResolveSinglePlayer(out var player) || string.IsNullOrWhiteSpace(player.PlayerId)) { return MapIdentityReject(); } if (!string.Equals(player.PlayerId, playerId.Trim(), StringComparison.Ordinal)) { return EvidenceReject(LocalPressureEvidenceWriteRejectReason.PlayerMismatch, "Record wipe player identity did not match the supported host player."); } string text = playerId.Trim(); LocalPressureRecordWipeResult localPressureRecordWipeResult; try { localPressureRecordWipeResult = LocalPressureTransitions.ApplyRecordWipe(GetOrCreateState(text), _profile); } catch (Exception ex) { _log("Local Pressure record wipe transition failed: " + ex.Message); return EvidenceReject(LocalPressureEvidenceWriteRejectReason.AdapterFailure, "Record wipe transition failed."); } _states[text] = localPressureRecordWipeResult.State; return new LocalPressureEvidenceWriteResult(Accepted: true, LocalPressureEvidenceWriteRejectReason.None, localPressureRecordWipeResult.State, "Record wipe accepted."); } } private void PublishTransition(LocalPressureTierTransitionNotification? notification) { if ((object)notification == null || _tierTransitionSink == null) { return; } try { _tierTransitionSink.Publish(notification); } catch (Exception ex) { _log("Local Pressure tier-transition publication failed: " + ex.Message); } } private void ResetTransitionSinkForEpoch() { if (_tierTransitionSink == null) { return; } try { _tierTransitionSink.ResetForEpoch(SessionEpoch, LoadEpoch); } catch (Exception ex) { _log("Local Pressure tier-transition reset failed: " + ex.Message); } } private void Reject(LocalPressureRuntimeRejectReason reason, bool quarantine) { SetRejectReason(reason); if (quarantine) { SetPhase(LocalPressureRuntimePhase.Quarantined); } } private void SetPhase(LocalPressureRuntimePhase phase) { if (_phase != phase) { _phase = phase; _receiptLog($"Local Pressure phase changed: {phase}; loadEpoch={LoadEpoch}."); } } private void SetRejectReason(LocalPressureRuntimeRejectReason reason) { if (LastRejectReason != reason) { LastRejectReason = reason; _log($"Local Pressure readiness/reject reason changed: {reason}; phase={_phase}; loadEpoch={LoadEpoch}."); } } private void ClearRejectReason() { SetRejectReason(LocalPressureRuntimeRejectReason.None); } } public sealed class LocalPressureStateStoreRepository : ILocalPressureStateRepository { private readonly LocalPressureStateStore _store; public LocalPressureStateStoreRepository(LocalPressureStateStore store) { _store = store ?? throw new ArgumentNullException("store"); } public LocalPressureStoreLoadResult Load() { if (!_store.TryLoad(out LocalPressureStoreLoadResult result)) { return result; } return result; } public LocalPressureStoreUpdateResult Update(LocalPressurePlayerRecord record) { if (!_store.TryUpdate(record, out LocalPressureStoreUpdateResult result)) { return result; } return result; } } public sealed class LocalPressureTierTransitionFanOut : ILocalPressureTierTransitionSink, IDisposable { private readonly ILocalPressureTierTransitionSink _first; private readonly ILocalPressureTierTransitionSink _second; private readonly Action _log; private bool _disposed; public LocalPressureTierTransitionFanOut(ILocalPressureTierTransitionSink first, ILocalPressureTierTransitionSink second, Action? log = null) { _first = first ?? throw new ArgumentNullException("first"); _second = second ?? throw new ArgumentNullException("second"); _log = log ?? ((Action)delegate { }); } public void Publish(LocalPressureTierTransitionNotification notification) { if (!_disposed) { Forward(delegate { _first.Publish(notification); }, "law response"); Forward(delegate { _second.Publish(notification); }, "Chief Campbell observer"); } } public void ResetForEpoch(Guid sessionEpoch, long loadEpoch) { if (!_disposed) { Forward(delegate { _first.ResetForEpoch(sessionEpoch, loadEpoch); }, "law response"); Forward(delegate { _second.ResetForEpoch(sessionEpoch, loadEpoch); }, "Chief Campbell observer"); } } public void Dispose() { if (!_disposed) { _disposed = true; } } private void Forward(Action action, string label) { try { action(); } catch (Exception ex) { _log("Local Pressure tier transition fan out to the " + label + " threw: " + ex.GetType().Name); } } } public sealed record NativeDispatchStationSnapshot(string StationIdentity, IReadOnlyList OfficerIdentities, int AvailableVehicleCount); public static class NativeDispatchOutcomeEvaluator { public static NativeLawResponseResult Evaluate(NativeDispatchStationSnapshot? before, NativeDispatchStationSnapshot? after, NativeLawResponseProfile profile, string correlationId) { if (!TryValidateSnapshot(before) || !TryValidateSnapshot(after)) { return Result(NativeLawResponseResultState.InconclusiveOutcome, correlationId, "station observation was unavailable"); } NativeLawResponseDiagnostics nativeLawResponseDiagnostics = Diagnostics(before.StationIdentity, before.OfficerIdentities.Count, OfficersConsumed(before, after), before.AvailableVehicleCount, before.AvailableVehicleCount - after.AvailableVehicleCount); if (before.OfficerIdentities.Count < profile.RequestedOfficerCount || before.AvailableVehicleCount < 1) { return Result(NativeLawResponseResultState.UnavailableCapacity, correlationId, "station preflight capacity was unavailable", nativeLawResponseDiagnostics); } if (!string.Equals(before.StationIdentity, after.StationIdentity, StringComparison.Ordinal) || nativeLawResponseDiagnostics.OfficersConsumed != profile.RequestedOfficerCount || nativeLawResponseDiagnostics.VehiclesConsumed != 1) { return Result(NativeLawResponseResultState.InconclusiveOutcome, correlationId, "native dispatch outcome did not match the expected station capacity delta", nativeLawResponseDiagnostics); } return Result(NativeLawResponseResultState.Accepted, correlationId, "native dispatch consumed the expected station capacity", nativeLawResponseDiagnostics); } private static bool TryValidateSnapshot(NativeDispatchStationSnapshot? snapshot) { if ((object)snapshot == null || string.IsNullOrWhiteSpace(snapshot.StationIdentity) || snapshot.OfficerIdentities == null) { return false; } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (string officerIdentity in snapshot.OfficerIdentities) { if (string.IsNullOrWhiteSpace(officerIdentity) || !hashSet.Add(officerIdentity)) { return false; } } return true; } private static int OfficersConsumed(NativeDispatchStationSnapshot before, NativeDispatchStationSnapshot after) { return before.OfficerIdentities.Except(after.OfficerIdentities).Count(); } private static NativeLawResponseDiagnostics Diagnostics(string stationIdentity, int officersBefore, int officersConsumed, int vehiclesBefore, int vehiclesConsumed) { return new NativeLawResponseDiagnostics(stationIdentity, officersBefore, officersConsumed, vehiclesBefore, vehiclesConsumed); } private static NativeLawResponseResult Result(NativeLawResponseResultState state, string correlationId, string reason, NativeLawResponseDiagnostics? diagnostics = null) { return new NativeLawResponseResult(state, correlationId, reason, diagnostics); } } public enum NativeLawResponseAdmissionRejectReason { None, StaleEpoch, MissingIdentity, MissingSourcePlayer, UnsupportedTierEdge, DuplicateCorrelation, InFlight, Disposed } public sealed record NativeLawResponseAdmissionResult(bool Accepted, NativeLawResponseAdmissionRejectReason RejectReason, NativeLawResponseRequest? Request); public sealed class NativeLawResponseAdmissionGate : IDisposable { private Guid _sessionEpoch; private long _loadEpoch; private bool _hasEpoch; private bool _disposed; private string? _inFlight; private readonly HashSet _consumed = new HashSet(StringComparer.Ordinal); public void ResetForEpoch(Guid sessionEpoch, long loadEpoch) { if (!_disposed && (!_hasEpoch || !(_sessionEpoch == sessionEpoch) || _loadEpoch != loadEpoch)) { _sessionEpoch = sessionEpoch; _loadEpoch = loadEpoch; _hasEpoch = true; _inFlight = null; _consumed.Clear(); } } public NativeLawResponseAdmissionResult TryBegin(LocalPressureTierTransitionNotification transition, NativeLawResponseProfile profile) { if (_disposed) { return Reject(NativeLawResponseAdmissionRejectReason.Disposed); } if (!_hasEpoch || transition.SessionEpoch != _sessionEpoch || transition.LoadEpoch != _loadEpoch) { return Reject(NativeLawResponseAdmissionRejectReason.StaleEpoch); } if (string.IsNullOrWhiteSpace(transition.PlayerId)) { return Reject(NativeLawResponseAdmissionRejectReason.MissingIdentity); } if (transition.SourcePlayer == null) { return Reject(NativeLawResponseAdmissionRejectReason.MissingSourcePlayer); } if (string.IsNullOrWhiteSpace(transition.CorrelationId)) { return Reject(NativeLawResponseAdmissionRejectReason.MissingIdentity); } if (!IsSupportedRisingEdge(transition.PreviousTier, transition.CurrentTier)) { return Reject(NativeLawResponseAdmissionRejectReason.UnsupportedTierEdge); } if (_inFlight != null) { return Reject(NativeLawResponseAdmissionRejectReason.InFlight); } if (_consumed.Contains(transition.CorrelationId)) { return Reject(NativeLawResponseAdmissionRejectReason.DuplicateCorrelation); } _inFlight = transition.CorrelationId; return new NativeLawResponseAdmissionResult(Accepted: true, NativeLawResponseAdmissionRejectReason.None, new NativeLawResponseRequest(transition.SessionEpoch, transition.LoadEpoch, transition.PlayerId, transition.SourcePlayer, transition.CorrelationId, transition.CurrentTier, transition.Region, transition.PropertyCode, profile)); } public void Complete(string correlationId) { if (!_disposed && !(_inFlight != correlationId)) { _consumed.Add(correlationId); _inFlight = null; } } public void Dispose() { _disposed = true; _inFlight = null; _consumed.Clear(); } private static bool IsSupportedRisingEdge(LocalPressureTier previous, LocalPressureTier current) { bool flag = current > previous; if (flag) { bool flag2 = (uint)(current - 2) <= 1u; flag = flag2; } return flag; } private static NativeLawResponseAdmissionResult Reject(NativeLawResponseAdmissionRejectReason reason) { return new NativeLawResponseAdmissionResult(Accepted: false, reason, null); } } public sealed record LocalPressureTierTransitionNotification { public Guid SessionEpoch { get; } public long LoadEpoch { get; } public string PlayerId { get; } public object? SourcePlayer { get; } public string CorrelationId { get; } public LocalPressureTier PreviousTier { get; } public LocalPressureTier CurrentTier { get; } public string? Region { get; } public string? PropertyCode { get; } public LocalPressureTierTransitionNotification(Guid SessionEpoch, long LoadEpoch, string PlayerId, object? SourcePlayer, string CorrelationId, LocalPressureTier PreviousTier, LocalPressureTier CurrentTier, string? Region, string? PropertyCode) { this.SessionEpoch = SessionEpoch; this.LoadEpoch = LoadEpoch; this.PlayerId = Required(PlayerId, "PlayerId"); this.SourcePlayer = SourcePlayer; this.CorrelationId = Required(CorrelationId, "CorrelationId"); this.PreviousTier = PreviousTier; this.CurrentTier = CurrentTier; this.Region = Region; this.PropertyCode = PropertyCode; base..ctor(); } private static string Required(string value, string name) { if (!string.IsNullOrWhiteSpace(value)) { return value; } throw new ArgumentException("Value is required.", name); } [CompilerGenerated] public void Deconstruct(out Guid SessionEpoch, out long LoadEpoch, out string PlayerId, out object? SourcePlayer, out string CorrelationId, out LocalPressureTier PreviousTier, out LocalPressureTier CurrentTier, out string? Region, out string? PropertyCode) { SessionEpoch = this.SessionEpoch; LoadEpoch = this.LoadEpoch; PlayerId = this.PlayerId; SourcePlayer = this.SourcePlayer; CorrelationId = this.CorrelationId; PreviousTier = this.PreviousTier; CurrentTier = this.CurrentTier; Region = this.Region; PropertyCode = this.PropertyCode; } } public sealed record NativeLawResponseProfile { public int RequestedOfficerCount { get; } public bool UseVehicle { get; } public bool BeginAsSighted { get; } public static NativeLawResponseProfile VehicleTwoOfficerV1 { get; } = new NativeLawResponseProfile(2, UseVehicle: true, BeginAsSighted: false); public NativeLawResponseProfile(int RequestedOfficerCount, bool UseVehicle, bool BeginAsSighted) { if (RequestedOfficerCount <= 0) { throw new ArgumentOutOfRangeException("RequestedOfficerCount"); } this.RequestedOfficerCount = RequestedOfficerCount; this.UseVehicle = UseVehicle; this.BeginAsSighted = BeginAsSighted; base..ctor(); } [CompilerGenerated] private NativeLawResponseProfile(NativeLawResponseProfile original) { RequestedOfficerCount = original.RequestedOfficerCount; UseVehicle = original.UseVehicle; BeginAsSighted = original.BeginAsSighted; } [CompilerGenerated] public void Deconstruct(out int RequestedOfficerCount, out bool UseVehicle, out bool BeginAsSighted) { RequestedOfficerCount = this.RequestedOfficerCount; UseVehicle = this.UseVehicle; BeginAsSighted = this.BeginAsSighted; } } public sealed record NativeLawResponseRequest { public Guid SessionEpoch { get; } public long LoadEpoch { get; } public string PlayerId { get; } public object SourcePlayer { get; } public string CorrelationId { get; } public LocalPressureTier TriggerTier { get; } public string? Region { get; } public string? PropertyCode { get; } public NativeLawResponseProfile Profile { get; } public NativeLawResponseRequest(Guid SessionEpoch, long LoadEpoch, string PlayerId, object SourcePlayer, string CorrelationId, LocalPressureTier TriggerTier, string? Region, string? PropertyCode, NativeLawResponseProfile Profile) { this.SessionEpoch = SessionEpoch; this.LoadEpoch = LoadEpoch; this.PlayerId = Required(PlayerId, "PlayerId"); this.SourcePlayer = SourcePlayer ?? throw new ArgumentNullException("SourcePlayer"); this.CorrelationId = Required(CorrelationId, "CorrelationId"); this.TriggerTier = TriggerTier; this.Region = Region; this.PropertyCode = PropertyCode; this.Profile = Profile ?? throw new ArgumentNullException("Profile"); base..ctor(); } private static string Required(string value, string name) { if (!string.IsNullOrWhiteSpace(value)) { return value; } throw new ArgumentException("Value is required.", name); } [CompilerGenerated] public void Deconstruct(out Guid SessionEpoch, out long LoadEpoch, out string PlayerId, out object SourcePlayer, out string CorrelationId, out LocalPressureTier TriggerTier, out string? Region, out string? PropertyCode, out NativeLawResponseProfile Profile) { SessionEpoch = this.SessionEpoch; LoadEpoch = this.LoadEpoch; PlayerId = this.PlayerId; SourcePlayer = this.SourcePlayer; CorrelationId = this.CorrelationId; TriggerTier = this.TriggerTier; Region = this.Region; PropertyCode = this.PropertyCode; Profile = this.Profile; } } public enum NativeLawResponseResultState { Accepted, Suppressed, Rejected, UnavailableCapacity, InconclusiveOutcome, AdapterUnavailable, AuthorityRejected, TargetRejected } public sealed record NativeLawResponseDiagnostics(string? StationIdentity, int? OfficersBefore, int? OfficersConsumed, int? VehiclesBefore, int? VehiclesConsumed); public sealed record NativeLawResponseResult { public NativeLawResponseResultState State { get; } public string CorrelationId { get; } public string Reason { get; } public NativeLawResponseDiagnostics? Diagnostics { get; } public NativeLawResponseResult(NativeLawResponseResultState State, string CorrelationId, string Reason, NativeLawResponseDiagnostics? Diagnostics = null) { this.State = State; this.CorrelationId = Required(CorrelationId, "CorrelationId"); this.Reason = Required(Reason, "Reason"); this.Diagnostics = Diagnostics; base..ctor(); } private static string Required(string value, string name) { if (!string.IsNullOrWhiteSpace(value)) { return value; } throw new ArgumentException("Value is required.", name); } [CompilerGenerated] public void Deconstruct(out NativeLawResponseResultState State, out string CorrelationId, out string Reason, out NativeLawResponseDiagnostics? Diagnostics) { State = this.State; CorrelationId = this.CorrelationId; Reason = this.Reason; Diagnostics = this.Diagnostics; } } public interface ILocalPressureTierTransitionSink { void Publish(LocalPressureTierTransitionNotification notification); void ResetForEpoch(Guid sessionEpoch, long loadEpoch); } public interface INativeLawResponseAdapter { NativeLawResponseResult TryRequest(NativeLawResponseRequest request); } public sealed class NativeLawResponseController : ILocalPressureTierTransitionSink, IDisposable { private readonly NativeLawResponseAdmissionGate _gate; private readonly INativeLawResponseAdapter _adapter; private readonly NativeLawResponseProfile _profile; private readonly Action _log; private bool _disposed; public NativeLawResponseResult? LastResult { get; private set; } public NativeLawResponseController(NativeLawResponseAdmissionGate gate, INativeLawResponseAdapter adapter, NativeLawResponseProfile profile, Action log) { _gate = gate ?? throw new ArgumentNullException("gate"); _adapter = adapter ?? throw new ArgumentNullException("adapter"); _profile = profile ?? throw new ArgumentNullException("profile"); _log = log ?? throw new ArgumentNullException("log"); } public void Publish(LocalPressureTierTransitionNotification notification) { if (_disposed) { return; } NativeLawResponseAdmissionResult nativeLawResponseAdmissionResult = _gate.TryBegin(notification, _profile); if (!nativeLawResponseAdmissionResult.Accepted || (object)nativeLawResponseAdmissionResult.Request == null) { LastResult = SuppressedOrRejected(notification.CorrelationId, nativeLawResponseAdmissionResult); return; } try { LastResult = _adapter.TryRequest(nativeLawResponseAdmissionResult.Request); } catch (Exception ex) { LastResult = new NativeLawResponseResult(NativeLawResponseResultState.AdapterUnavailable, nativeLawResponseAdmissionResult.Request.CorrelationId, "Native law-response adapter threw: " + ex.GetType().Name); } finally { _gate.Complete(nativeLawResponseAdmissionResult.Request.CorrelationId); } _log(FormatBoundedResult(LastResult)); } public void ResetForEpoch(Guid sessionEpoch, long loadEpoch) { if (!_disposed) { _gate.ResetForEpoch(sessionEpoch, loadEpoch); } } public void Dispose() { if (!_disposed) { _disposed = true; _gate.Dispose(); } } private static NativeLawResponseResult SuppressedOrRejected(string correlationId, NativeLawResponseAdmissionResult admission) { NativeLawResponseAdmissionRejectReason rejectReason = admission.RejectReason; bool flag = (uint)(rejectReason - 4) <= 2u; return new NativeLawResponseResult(flag ? NativeLawResponseResultState.Suppressed : NativeLawResponseResultState.Rejected, correlationId, admission.RejectReason.ToString()); } private static string FormatBoundedResult(NativeLawResponseResult result) { return $"Native law response {result.State} for {result.CorrelationId}: {result.Reason}"; } } internal sealed class NativeLawResponseModComposition : IDisposable { private readonly IDisposable _runtimeLifetime; private readonly IDisposable _controllerLifetime; private bool _disposed; public ILocalPressureRuntimeHostAdapter HostAdapter { get; } public NativeLawResponseController Controller { get; } public Release1ChiefTierObserver ChiefObserver { get; } public ILocalPressureTierTransitionSink TierTransitionSink { get; } public LocalPressureRuntimeComposition LocalPressureRuntime { get; } public NativeLawResponseModComposition(Func repositoryFactory, Action warningLog, Action infoLog, Action? receiptLog = null) : this(new LocalPressureHostLifecycleAdapter(warningLog), repositoryFactory, warningLog, infoLog, null, null, null, null, receiptLog) { } internal NativeLawResponseModComposition(ILocalPressureRuntimeHostAdapter hostAdapter, Func repositoryFactory, Action warningLog, Action infoLog, Func, NativeLawResponseController>? controllerFactory = null, Func, Action, ILocalPressureTierTransitionSink, LocalPressureRuntimeComposition>? runtimeFactory = null, Func? runtimeLifetimeFactory = null, Func? controllerLifetimeFactory = null, Action? receiptLog = null) { HostAdapter = hostAdapter ?? throw new ArgumentNullException("hostAdapter"); ArgumentNullException.ThrowIfNull(repositoryFactory, "repositoryFactory"); ArgumentNullException.ThrowIfNull(warningLog, "warningLog"); ArgumentNullException.ThrowIfNull(infoLog, "infoLog"); if (controllerFactory == null) { controllerFactory = CreateController; } if (runtimeFactory == null) { runtimeFactory = (ILocalPressureRuntimeHostAdapter adapter, Func factory, Action log, ILocalPressureTierTransitionSink sink) => CreateRuntime(adapter, factory, log, sink, receiptLog); } NativePoliceDispatchAdapter arg = new NativePoliceDispatchAdapter(HostAdapter, warningLog); Controller = controllerFactory(new NativeLawResponseAdmissionGate(), arg, NativeLawResponseProfile.VehicleTwoOfficerV1, infoLog); ChiefObserver = new Release1ChiefTierObserver(); TierTransitionSink = new LocalPressureTierTransitionFanOut(Controller, ChiefObserver, warningLog); LocalPressureRuntime = runtimeFactory(HostAdapter, repositoryFactory, warningLog, TierTransitionSink); IDisposable runtimeLifetime; if (runtimeLifetimeFactory != null) { runtimeLifetime = runtimeLifetimeFactory(LocalPressureRuntime); } else { IDisposable localPressureRuntime = LocalPressureRuntime; runtimeLifetime = localPressureRuntime; } _runtimeLifetime = runtimeLifetime; IDisposable controllerLifetime; if (controllerLifetimeFactory != null) { controllerLifetime = controllerLifetimeFactory(Controller); } else { IDisposable localPressureRuntime = Controller; controllerLifetime = localPressureRuntime; } _controllerLifetime = controllerLifetime; } public void Dispose() { if (!_disposed) { _disposed = true; (TierTransitionSink as IDisposable)?.Dispose(); _runtimeLifetime.Dispose(); _controllerLifetime.Dispose(); } } private static NativeLawResponseController CreateController(NativeLawResponseAdmissionGate gate, INativeLawResponseAdapter adapter, NativeLawResponseProfile profile, Action log) { return new NativeLawResponseController(gate, adapter, profile, log); } private static LocalPressureRuntimeComposition CreateRuntime(ILocalPressureRuntimeHostAdapter adapter, Func repositoryFactory, Action log, ILocalPressureTierTransitionSink sink, Action? receiptLog = null) { return new LocalPressureRuntimeComposition(adapter, repositoryFactory, log, sink, receiptLog); } } internal interface INativePoliceDispatchBoundary { bool IsMorePatrolsPresent(); bool TryGetCanonicalTarget(object sourcePlayer, out object canonicalTarget); bool TryReadTargetIdentity(object canonicalTarget, out bool isServerInitialized, out object? connection, out string? playerCode, out string failure); object? GetClosestPoliceStation(object canonicalTarget); bool TrySnapshot(object station, out NativeDispatchStationSnapshot snapshot); void RequestResponse(object station, object canonicalTarget); } internal sealed class NativePoliceDispatchBoundary : INativePoliceDispatchBoundary { public bool IsMorePatrolsPresent() { return AppDomain.CurrentDomain.GetAssemblies().Any((Assembly assembly) => string.Equals(assembly.GetName().Name, "MorePatrols", StringComparison.OrdinalIgnoreCase)); } public bool TryGetCanonicalTarget(object sourcePlayer, out object canonicalTarget) { Player val = (Player)((sourcePlayer is Player) ? sourcePlayer : null); if (val != null) { canonicalTarget = val; return true; } canonicalTarget = null; return false; } public bool TryReadTargetIdentity(object canonicalTarget, out bool isServerInitialized, out object? connection, out string? playerCode, out string failure) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0007: Expected O, but got Unknown try { Player val = (Player)canonicalTarget; isServerInitialized = ((NetworkBehaviour)val).IsServerInitialized; connection = val.Connection; playerCode = val.PlayerCode?.Trim(); failure = string.Empty; return true; } catch (Exception ex) { isServerInitialized = false; connection = null; playerCode = null; failure = "native player identity read failed: " + ex.GetType().Name; return false; } } public object? GetClosestPoliceStation(object canonicalTarget) { //IL_0001: 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) return PoliceStation.GetClosestPoliceStation(((Component)(Player)canonicalTarget).transform.position); } public bool TrySnapshot(object stationHandle, out NativeDispatchStationSnapshot snapshot) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown snapshot = null; try { PoliceStation val = (PoliceStation)stationHandle; List list = new List(); List officerPool = val.OfficerPool; if (officerPool == null) { return false; } Enumerator enumerator = officerPool.GetEnumerator(); while (enumerator.MoveNext()) { PoliceOfficer current = enumerator.Current; if (current != null) { list.Add(((Object)current).GetInstanceID().ToString(CultureInfo.InvariantCulture)); } } snapshot = new NativeDispatchStationSnapshot(((Object)val).GetInstanceID().ToString(CultureInfo.InvariantCulture), list, val.AvailableVehicleCount); return true; } catch { snapshot = null; return false; } } public void RequestResponse(object stationHandle, object canonicalTargetHandle) { //IL_0001: 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) //IL_000d: Expected O, but got Unknown PoliceStation val = (PoliceStation)stationHandle; Player val2 = (Player)canonicalTargetHandle; val.Dispatch(2, val2, (EDispatchType)1, false); } } public sealed class NativePoliceDispatchAdapter : INativeLawResponseAdapter { private readonly ILocalPressureRuntimeHostAdapter _runtimeHost; private readonly INativePoliceDispatchBoundary _nativeBoundary; private readonly Action _log; private bool _morePatrolsSuppressionLogged; public NativePoliceDispatchAdapter(ILocalPressureRuntimeHostAdapter runtimeHost, Action? log = null) : this(runtimeHost, new NativePoliceDispatchBoundary(), log) { } internal NativePoliceDispatchAdapter(ILocalPressureRuntimeHostAdapter runtimeHost, INativePoliceDispatchBoundary nativeBoundary, Action? log = null) { _runtimeHost = runtimeHost ?? throw new ArgumentNullException("runtimeHost"); _nativeBoundary = nativeBoundary ?? throw new ArgumentNullException("nativeBoundary"); _log = log ?? ((Action)delegate { }); } public NativeLawResponseResult TryRequest(NativeLawResponseRequest request) { if (!TryReadAuthoritativeSinglePlayerHost(request, out LocalPressurePlayerSample sample, out NativeLawResponseResult failure)) { return failure; } if (IsMorePatrolsPresent()) { return Result(NativeLawResponseResultState.Rejected, request, "MorePatrols is loaded; coexistence is not approved"); } if (!TryValidateCanonicalTarget(request, sample, out object target, out string failure2)) { return Result(NativeLawResponseResultState.TargetRejected, request, failure2); } object closestPoliceStation; try { closestPoliceStation = _nativeBoundary.GetClosestPoliceStation(target); } catch (Exception exception) { return AdapterFailure(request, "closest station", exception); } if (closestPoliceStation == null) { return Result(NativeLawResponseResultState.AdapterUnavailable, request, "closest police station was unavailable"); } if (!_nativeBoundary.TrySnapshot(closestPoliceStation, out NativeDispatchStationSnapshot snapshot)) { return Result(NativeLawResponseResultState.AdapterUnavailable, request, "station preflight could not be observed"); } if (snapshot.OfficerIdentities.Count < 2 || snapshot.AvailableVehicleCount < 1) { return UnavailableCapacity(request, snapshot); } try { _nativeBoundary.RequestResponse(closestPoliceStation, target); } catch (Exception exception2) { return AdapterFailure(request, "Dispatch", exception2); } if (!_nativeBoundary.TrySnapshot(closestPoliceStation, out NativeDispatchStationSnapshot snapshot2)) { return Result(NativeLawResponseResultState.InconclusiveOutcome, request, "post-dispatch outcome was unavailable"); } return NativeDispatchOutcomeEvaluator.Evaluate(snapshot, snapshot2, request.Profile, request.CorrelationId); } private bool TryReadAuthoritativeSinglePlayerHost(NativeLawResponseRequest request, out LocalPressurePlayerSample sample, out NativeLawResponseResult? failure) { sample = default(LocalPressurePlayerSample); failure = null; switch (_runtimeHost.ReadHostAuthority()) { case LocalPressureHostAuthorityReadStatus.Faulted: failure = Result(NativeLawResponseResultState.AdapterUnavailable, request, "host authority read faulted"); return false; default: failure = Result(NativeLawResponseResultState.AuthorityRejected, request, "authoritative single-player host was unavailable"); return false; case LocalPressureHostAuthorityReadStatus.Ready: { IReadOnlyList players; switch (_runtimeHost.TryReadSupportedPlayers(out players)) { case LocalPressurePlayerReadStatus.Ready: if (players.Count == 1) { sample = players[0]; return true; } break; case LocalPressurePlayerReadStatus.Faulted: failure = Result(NativeLawResponseResultState.AdapterUnavailable, request, "supported player read faulted"); return false; } failure = Result(NativeLawResponseResultState.TargetRejected, request, "supported single-player host target was unavailable"); return false; } } } private bool TryValidateCanonicalTarget(NativeLawResponseRequest request, LocalPressurePlayerSample sample, out object target, out string failure) { target = null; failure = string.Empty; if (sample.SourcePlayer != request.SourcePlayer) { failure = "supported player sample did not match the requested source player"; return false; } if (!_nativeBoundary.TryGetCanonicalTarget(sample.SourcePlayer, out target)) { failure = "requested source player was not the native Player interop type"; return false; } if (!sample.IsHostOwned || !sample.HasConnection) { failure = "requested source player was not host-owned and connected"; return false; } if (!_nativeBoundary.TryReadTargetIdentity(target, out bool isServerInitialized, out object connection, out string playerCode, out failure)) { return false; } if (!isServerInitialized || connection == null) { failure = "native player was not server-initialized and connected"; return false; } if (!LocalPressureHostLifecyclePolicies.IsCanonicalPlayerIdentity(playerCode) || !string.Equals(playerCode, request.PlayerId, StringComparison.Ordinal)) { failure = "native player identity did not match the canonical request identity"; return false; } return true; } private bool IsMorePatrolsPresent() { bool num = _nativeBoundary.IsMorePatrolsPresent(); if (num && !_morePatrolsSuppressionLogged) { _morePatrolsSuppressionLogged = true; _log("MorePatrols is loaded; native police dispatch response suppressed."); } return num; } private static NativeLawResponseResult UnavailableCapacity(NativeLawResponseRequest request, NativeDispatchStationSnapshot before) { return new NativeLawResponseResult(NativeLawResponseResultState.UnavailableCapacity, request.CorrelationId, "station preflight capacity was unavailable", new NativeLawResponseDiagnostics(before.StationIdentity, before.OfficerIdentities.Count, null, before.AvailableVehicleCount, null)); } private static NativeLawResponseResult AdapterFailure(NativeLawResponseRequest request, string operation, Exception exception) { return Result(NativeLawResponseResultState.AdapterUnavailable, request, "native police dispatch " + operation + " failed: " + exception.GetType().Name); } private static NativeLawResponseResult Result(NativeLawResponseResultState state, NativeLawResponseRequest request, string reason) { return new NativeLawResponseResult(state, request.CorrelationId, reason); } } public sealed class OcRelease1PhoneCallDefinition : PhoneCallDefinition { public PhoneCallData Data => base.S1PhoneCallData; public string CallerName { get { CallerID callerID = Data.CallerID; return ((callerID != null) ? callerID.Name : null) ?? string.Empty; } } public IReadOnlyList StageTexts { get { if (Data.Stages != null) { return ((IEnumerable)Data.Stages).Select((Stage stage) => ((stage != null) ? stage.Text : null) ?? string.Empty).ToArray(); } return Array.Empty(); } } public OcRelease1PhoneCallDefinition(string callerName, params string[] stages) : base(callerName, (Sprite)null) { if (string.IsNullOrWhiteSpace(callerName)) { throw new ArgumentException("Caller name is required.", "callerName"); } ArgumentNullException.ThrowIfNull(stages, "stages"); if (stages.Length == 0) { throw new ArgumentException("At least one phone stage is required.", "stages"); } foreach (string text in stages) { if (string.IsNullOrWhiteSpace(text)) { throw new ArgumentException("Stage text is required.", "stages"); } ((PhoneCallDefinition)this).AddStage(text); } } } public static class OrganizedCrimeLog { public static Action Warning { get; set; } = delegate { }; public static Action Receipt { get; set; } = delegate { }; } public sealed class OrganizedCrimeTimingReceipts { public const int DefaultThresholdMs = 50; public const string ReceiptPrefix = "[Organized Crime] timing: "; public static readonly OrganizedCrimeTimingReceipts Disabled = new OrganizedCrimeTimingReceipts(null, 0); private readonly Action? _sink; private readonly int _thresholdMs; public int ThresholdMs => _thresholdMs; public bool IsEnabled { get { if (_sink != null) { return _thresholdMs > 0; } return false; } } public OrganizedCrimeTimingReceipts(Action? sink, int thresholdMs = 50) { _sink = sink; _thresholdMs = thresholdMs; } public void Measure(string phase, Action work) { ArgumentNullException.ThrowIfNull(work, "work"); if (!IsEnabled) { work(); return; } Stopwatch stopwatch = Stopwatch.StartNew(); try { work(); } finally { stopwatch.Stop(); Report(phase, stopwatch.Elapsed.TotalMilliseconds); } } public T Measure(string phase, Func work) { ArgumentNullException.ThrowIfNull(work, "work"); if (!IsEnabled) { return work(); } Stopwatch stopwatch = Stopwatch.StartNew(); try { return work(); } finally { stopwatch.Stop(); Report(phase, stopwatch.Elapsed.TotalMilliseconds); } } public void Report(string phase, double elapsedMs) { if (!IsEnabled || string.IsNullOrWhiteSpace(phase) || !double.IsFinite(elapsedMs) || elapsedMs < (double)_thresholdMs) { return; } try { _sink(FormatReceipt(phase, elapsedMs)); } catch { } } public static string FormatReceipt(string phase, double elapsedMs) { return $"{"[Organized Crime] timing: "}{phase} {(long)Math.Round(elapsedMs, MidpointRounding.AwayFromZero)} ms"; } } public static class OwnerQaKeysPreference { private const string CategoryIdentifier = "OrganizedCrime"; private const string EntryIdentifier = "OwnerQaKeys"; private const bool DefaultValue = false; public static bool Read() { MelonPreferences_Category val = MelonPreferences.GetCategory("OrganizedCrime") ?? MelonPreferences.CreateCategory("OrganizedCrime"); return (val.GetEntry("OwnerQaKeys") ?? val.CreateEntry("OwnerQaKeys", false, (string)null, (string)null, false, false, (ValueValidator)null, (string)null)).Value; } } public readonly record struct CustodyPlayerObservation(object? SourcePlayer, string? PlayerCode, bool IsHostOwned, bool HasConnection, bool IsArrested, string? Region, string? PropertyCode, bool ArrestStateReadSucceeded = true); public readonly record struct PoliceCustodyPrefixState(bool Eligible, CustodyEntryOccurrence? Occurrence) { public static PoliceCustodyPrefixState Ineligible => new PoliceCustodyPrefixState(Eligible: false, null); } public sealed class PoliceCustodyEvidenceBridge : IDisposable { private readonly CustodyEntryGate _gate; private readonly ILocalPressureCustodyEvidenceWriter _writer; private readonly Action _log; private Guid? _gateSessionEpoch; private long _gateLoadEpoch; private bool _disposed; public PoliceCustodyEvidenceBridge(CustodyEntryGate gate, ILocalPressureCustodyEvidenceWriter writer, Action? log = null) { _gate = gate ?? throw new ArgumentNullException("gate"); _writer = writer ?? throw new ArgumentNullException("writer"); _log = log ?? ((Action)delegate { }); } public PoliceCustodyPrefixState CapturePrefix(CustodyPlayerObservation observation) { if (_disposed || !observation.ArrestStateReadSucceeded || observation.IsArrested || !observation.IsHostOwned || !observation.HasConnection) { return PoliceCustodyPrefixState.Ineligible; } LocalPressureActiveEvidenceSnapshot snapshot; try { if (!_writer.TryGetActiveEvidenceSnapshot(out snapshot)) { return PoliceCustodyPrefixState.Ineligible; } } catch (Exception ex) { _log("Police custody evidence snapshot read failed: " + ex.Message); return PoliceCustodyPrefixState.Ineligible; } if (!IsExactSupportedPlayer(observation, snapshot) || !IsConsistentPlayerCode(observation.PlayerCode, snapshot.PlayerId)) { return PoliceCustodyPrefixState.Ineligible; } EnsureGateEpoch(snapshot); _gate.ObserveNotArrested(snapshot.PlayerId, snapshot.SessionEpoch, snapshot.LoadEpoch); var (region, propertyCode) = NormalizeContext(observation.Region, observation.PropertyCode); return new PoliceCustodyPrefixState(Eligible: true, new CustodyEntryOccurrence(snapshot.PlayerId, snapshot.SessionEpoch, snapshot.LoadEpoch, region, propertyCode)); } public void ConfirmPostfix(CustodyPlayerObservation observation, PoliceCustodyPrefixState prefixState) { if (_disposed || !prefixState.Eligible || (object)prefixState.Occurrence == null || !observation.ArrestStateReadSucceeded || !observation.IsArrested || !observation.IsHostOwned || !observation.HasConnection) { return; } LocalPressureActiveEvidenceSnapshot snapshot; try { if (!_writer.TryGetActiveEvidenceSnapshot(out snapshot)) { return; } } catch (Exception ex) { _log("Police custody evidence snapshot confirmation failed: " + ex.Message); return; } if (!IsExactSupportedPlayer(observation, snapshot) || !IsConsistentPlayerCode(observation.PlayerCode, snapshot.PlayerId)) { return; } CustodyEntryOccurrence occurrence = prefixState.Occurrence; if (!string.Equals(occurrence.PlayerId, snapshot.PlayerId, StringComparison.Ordinal) || occurrence.SessionEpoch != snapshot.SessionEpoch || occurrence.LoadEpoch != snapshot.LoadEpoch) { return; } CustodyEntryResult custodyEntryResult = _gate.TryCommit(occurrence, snapshot.PlayerId, snapshot.SessionEpoch, snapshot.LoadEpoch, isArrestedAfterCall: true); if (!custodyEntryResult.Accepted || custodyEntryResult.CorrelationId == null || (object)custodyEntryResult.Occurrence == null) { return; } try { _writer.TryApplyCustodyEvidence(new CustodyEntryEvidence(custodyEntryResult.Occurrence.PlayerId, custodyEntryResult.CorrelationId, custodyEntryResult.Occurrence.Region, custodyEntryResult.Occurrence.PropertyCode), custodyEntryResult.Occurrence.SessionEpoch, custodyEntryResult.Occurrence.LoadEpoch); } catch (Exception ex2) { _log("Police custody evidence submission failed: " + ex2.Message); } } public void Dispose() { if (!_disposed) { _disposed = true; _gate.ResetForEpoch(Guid.Empty, 0L); } } private void EnsureGateEpoch(LocalPressureActiveEvidenceSnapshot snapshot) { Guid? gateSessionEpoch = _gateSessionEpoch; Guid sessionEpoch = snapshot.SessionEpoch; if (!gateSessionEpoch.HasValue || !(gateSessionEpoch.GetValueOrDefault() == sessionEpoch) || _gateLoadEpoch != snapshot.LoadEpoch) { _gateSessionEpoch = snapshot.SessionEpoch; _gateLoadEpoch = snapshot.LoadEpoch; _gate.ResetForEpoch(snapshot.SessionEpoch, snapshot.LoadEpoch); } } private static bool IsExactSupportedPlayer(CustodyPlayerObservation observation, LocalPressureActiveEvidenceSnapshot snapshot) { if (observation.SourcePlayer != null && snapshot.SupportedPlayer != null) { return observation.SourcePlayer == snapshot.SupportedPlayer; } return false; } private static bool IsConsistentPlayerCode(string? playerCode, string canonicalPlayerId) { playerCode = playerCode?.Trim(); if (playerCode == null || !playerCode.Contains('\0')) { if (LocalPressureHostLifecyclePolicies.IsCanonicalPlayerIdentity(playerCode)) { return string.Equals(playerCode, canonicalPlayerId, StringComparison.Ordinal); } return true; } return false; } private static (string? Region, string? PropertyCode) NormalizeContext(string? region, string? propertyCode) { region = NormalizeContextValue(region); propertyCode = NormalizeContextValue(propertyCode); if (region != null && propertyCode != null) { return (Region: region, PropertyCode: propertyCode); } return (Region: null, PropertyCode: null); } private static string? NormalizeContextValue(string? value) { if (string.IsNullOrWhiteSpace(value)) { return null; } string text = value.Trim(); if (text.Length > 128 || text.Any(char.IsControl)) { return null; } return text; } } public static class PoliceCustodyPatch { public const string TargetMethodName = "RpcLogic___Arrest_Server_2166136261"; private static PoliceCustodyEvidenceBridge? _bridge; private static Action _log = delegate { }; public static IReadOnlyList DeclaredTargetNames { get; } = new string[1] { "RpcLogic___Arrest_Server_2166136261" }; public static MethodInfo ResolveTarget() { MethodInfo methodInfo = AccessTools.Method(typeof(Player), "RpcLogic___Arrest_Server_2166136261", Type.EmptyTypes, (Type[])null); if ((object)methodInfo == null || methodInfo.ReturnType != typeof(void) || methodInfo.DeclaringType != typeof(Player)) { throw new MissingMethodException(typeof(Player).FullName, "RpcLogic___Arrest_Server_2166136261"); } return methodInfo; } public static bool Apply(Harmony harmony, PoliceCustodyEvidenceBridge bridge, Action? log = null) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown //IL_0079: Expected O, but got Unknown ArgumentNullException.ThrowIfNull(harmony, "harmony"); ArgumentNullException.ThrowIfNull(bridge, "bridge"); _log = log ?? ((Action)delegate { }); try { MethodInfo methodInfo = ResolveTarget(); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(typeof(PoliceCustodyPatch), "Prefix", (Type[])null), new HarmonyMethod(typeof(PoliceCustodyPatch), "Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _bridge = bridge; return true; } catch (Exception ex) { _bridge = null; _log("Police custody patch was not applied: " + ex.GetType().Name + ": " + ex.Message); return false; } } public static void Reset() { _bridge = null; _log = delegate { }; } private static void Prefix(Player __instance, out PoliceCustodyPrefixState __state) { __state = PoliceCustodyPrefixState.Ineligible; PoliceCustodyEvidenceBridge bridge = _bridge; if (bridge == null) { return; } try { __state = bridge.CapturePrefix(Observe(__instance)); } catch (Exception ex) { _log("Police custody prefix observation failed closed: " + ex.GetType().Name + ": " + ex.Message); } } private static void Postfix(Player __instance, PoliceCustodyPrefixState __state) { PoliceCustodyEvidenceBridge bridge = _bridge; if (bridge == null) { return; } try { bridge.ConfirmPostfix(Observe(__instance), __state); } catch (Exception ex) { _log("Police custody postfix observation failed closed: " + ex.GetType().Name + ": " + ex.Message); } } private static CustodyPlayerObservation Observe(Player instance) { if (instance == null) { return default(CustodyPlayerObservation); } string playerCode = ReadPlayerCode(instance); bool isHostOwned = ReadHostOwnership(instance); bool hasConnection = ReadConnection(instance); (bool, bool) tuple = ReadArrested(instance); string region = ReadRegion(instance); string propertyCode = ReadPropertyCode(instance); return new CustodyPlayerObservation(instance, playerCode, isHostOwned, hasConnection, tuple.Item2, region, propertyCode, tuple.Item1); } private static string? ReadPlayerCode(Player instance) { try { return instance.PlayerCode?.Trim(); } catch { return null; } } private static bool ReadHostOwnership(Player instance) { try { return ((NetworkBehaviour)instance).IsServerInitialized; } catch { return false; } } private static bool ReadConnection(Player instance) { try { return instance.Connection != null; } catch { return false; } } private static (bool Succeeded, bool Value) ReadArrested(Player instance) { try { return (Succeeded: true, Value: instance.IsArrested); } catch { return (Succeeded: false, Value: false); } } private static string? ReadRegion(Player instance) { //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) try { return ((object)instance.CurrentRegion/*cast due to .constrained prefix*/).ToString(); } catch { return null; } } private static string? ReadPropertyCode(Player instance) { try { Property currentProperty = instance.CurrentProperty; return (currentProperty != null) ? currentProperty.PropertyCode : null; } catch { return null; } } } public static class Release1ChiefCampbellCopy { private static readonly CultureInfo Invariant = CultureInfo.InvariantCulture; public const string ContactFirstName = "Chief"; public const string ContactLastName = "Campbell"; public const int FirstDemand = 15000; public const int SecondDemand = 20000; public const int ThirdDemand = 25000; public const string FirstDemandText = "This is Chief Campbell of the Hyland Point police. Your file is on my desk. Prints, statements, an arrest record with your name on it. Fifteen thousand in cash and the file is gone."; public const string SecondDemandText = "Chief Campbell again. My officers booked you in, so the file is thicker and the price is twenty thousand."; public const string ThirdDemandText = "Chief Campbell. Twenty five thousand. That is the last number I write down. It does not rise again and it does not go away."; public const string DeclineReplyText = "Your choice. The file stays where it is. It gets thicker every time my officers pick you up, and the number goes up with it."; public const string ShortCashFormat = "You are short. The number is {0} in cash, in hand. Come back when you have it."; public const string PaidText = "Received. The file is shredded, your name is off the board, and my officers have no reason to know you. Do not give them one."; public const string WatchListText = "Chief Campbell. You are on the watch list now. My officers are told to look at you twice. That is all it is, for now."; public const string LockdownAnnouncementText = "Chief Campbell. The town goes to curfew tonight and stays there, and it is because of you. Every hour is curfew and my officers will arrest you on sight. Pay what you owe, or stay quiet long enough for this to cool."; public const string LiftPaidText = "Curfew is lifted. Consider the account settled."; public const string LiftCooledText = "Curfew is lifted. You went quiet long enough. The file is still on my desk."; public const string PaymentBlockedText = "Your payment could not be confirmed. The demand still stands."; public const string DeclineLabel = "Not today"; public const string FallbackLogText = "Chief Campbell has no fallback presenter; he stays silent while the presentation mode is ImguiFallback."; public static int DemandFor(int round) { return Release1ChiefDemandLadder.For(round); } public static string Dollars(int amount) { return amount.ToString("0", Invariant); } public static string DemandText(int round) { if (round > 1) { if (round != 2) { return "Chief Campbell. Twenty five thousand. That is the last number I write down. It does not rise again and it does not go away."; } return "Chief Campbell again. My officers booked you in, so the file is thicker and the price is twenty thousand."; } return "This is Chief Campbell of the Hyland Point police. Your file is on my desk. Prints, statements, an arrest record with your name on it. Fifteen thousand in cash and the file is gone."; } public static string ShortCashText(int demand) { return string.Format(Invariant, "You are short. The number is {0} in cash, in hand. Come back when you have it.", Dollars(demand)); } public static string PayLabel(int demand) { return "Pay " + Dollars(demand); } } public sealed class Release1ChiefCampbellNpc : NPC, IRelease1MessagingNpc { private const string DefaultPortraitNpcId = "officerdavis"; private readonly Dictionary _loadedResponses = new Dictionary(StringComparer.Ordinal); public IReadOnlyDictionary LoadedResponses => _loadedResponses; public Release1ChiefCampbellNpc() : base("oc_release1_chief_campbell", "Chief", "Campbell", (Sprite)null) { TryApplyPortrait(); } private void TryApplyPortrait() { string text = Release1ChiefPortraitNpcIdPreference.Read(); if (string.IsNullOrEmpty(text)) { text = "officerdavis"; } try { NPC val = NPC.Get(text); if (((val != null) ? val.Icon : null) == null) { MelonLogger.Msg("[Organized Crime] Chief Campbell portrait: no icon found on '" + text + "'; keeping the default."); return; } ((NPC)this).Icon = val.Icon; ((NPC)this).RefreshMessagingIcons(); MelonLogger.Msg("[Organized Crime] Chief Campbell portrait borrowed from NPC '" + text + "'."); } catch (Exception ex) { MelonLogger.Msg($"[Organized Crime] Chief Campbell portrait borrow from '{text}' failed ({ex.GetType().Name}); keeping the default."); } } protected override void OnResponseLoaded(Response response) { if (response != null) { string label = response.Label; if (label != null) { _loadedResponses[Release1PlayerCopy.Normalize(label)] = response; } } } public bool TryGetResponse(string label, out Response? response) { return _loadedResponses.TryGetValue(Release1PlayerCopy.Normalize(label), out response); } } public sealed class Release1ChiefComposition : IDisposable { private sealed class ChiefCommandSink : IRelease1PresentationCommandSink { private readonly Release1ChiefService _service; public ChiefCommandSink(Release1ChiefService service) { _service = service; } public bool TryInvoke(Release1PresentationCommand command) { return command switch { Release1PresentationCommand.ChiefCampbellPay => _service.TryPay(), Release1PresentationCommand.ChiefCampbellDecline => _service.TryDecline(), _ => false, }; } } private readonly Release1PresentationMode _mode; private readonly Action _log; private bool _disposed; public Release1ChiefService Service { get; } public Release1PresentationProjector? Projector { get; } public Release1ChiefComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1ChiefTierObserver observer, Release1ChiefLedgerReader readLedger, Release1ChiefLedgerWiper wipeLedger, Release1ChiefEpochReader epochs, Release1PresentationMode mode = Release1PresentationMode.ImguiFallback, IRelease1NativePresentation? native = null, Action? log = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(world, "world"); ArgumentNullException.ThrowIfNull(observer, "observer"); ArgumentNullException.ThrowIfNull(readLedger, "readLedger"); ArgumentNullException.ThrowIfNull(wipeLedger, "wipeLedger"); ArgumentNullException.ThrowIfNull(epochs, "epochs"); if (mode == Release1PresentationMode.Native && native == null) { throw new ArgumentException("A native presentation implementation is required in Native mode.", "native"); } _mode = mode; _log = log ?? ((Action)delegate { }); Service = new Release1ChiefService(story, world, observer, readLedger, wipeLedger, epochs, LocalPressureProfile.Moderate); if (mode == Release1PresentationMode.Native) { Projector = new Release1PresentationProjector(story, () => Release1ChiefPresentation.BuildPlan(story.State), native, new ChiefCommandSink(Service), log, reconcileQuests: false); } } public void OnLoadComplete() { if (!_disposed) { Service.OnLoadComplete(); if (_mode == Release1PresentationMode.ImguiFallback) { _log("Chief Campbell has no fallback presenter; he stays silent while the presentation mode is ImguiFallback."); } if (Projector != null) { Projector.OnLoadComplete(); } } } public void OnPreLoad() { if (!_disposed) { if (Projector != null) { Projector.OnPreLoad(); } Service.OnPreLoad(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); if (Projector != null) { Projector.OnSaveStart(); } } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); if (Projector != null) { Projector.OnSaveComplete(); } } } public void Update() { if (!_disposed) { Service.Update(); Projector?.Reconcile(); } } public void Dispose() { if (!_disposed) { _disposed = true; Projector?.Dispose(); Service.Dispose(); } } } public delegate LocalPressureState? Release1ChiefLedgerReader(string playerId); public delegate LocalPressureEvidenceWriteResult Release1ChiefLedgerWiper(string playerId); public delegate bool Release1ChiefEpochReader(out Guid sessionEpoch, out long loadEpoch); public enum Release1ChiefAction { None, OpenDemand, ReplyDeclined, RecordWatchLine, AnnounceLockdown, LiftCooled, LiftPaid } public sealed record Release1ChiefDecision(Release1ChiefRecord Next, Release1ChiefAction Action); public static class Release1ChiefLedger { public static bool IsUnpaid(Release1ChiefRecord record) { Release1ChiefState state = record.State; if (state == Release1ChiefState.DemandOpen || state == Release1ChiefState.Declined) { return true; } return false; } public static Release1ChiefDecision Observe(Release1ChiefRecord record, Release1ChiefObservedEvent observed) { ArgumentNullException.ThrowIfNull(record, "record"); Release1ChiefRecord release1ChiefRecord = record with { Revision = record.Revision + 1 }; if (observed == Release1ChiefObservedEvent.ArrestObserved) { switch (record.State) { case Release1ChiefState.Adopted: case Release1ChiefState.Settled: return new Release1ChiefDecision(release1ChiefRecord with { State = Release1ChiefState.DemandOpen, DemandRound = 1, LastShortfallNoticed = null, AcceptedLogicalCorrelations = Array.Empty(), NativeEffectIds = Array.Empty() }, Release1ChiefAction.OpenDemand); case Release1ChiefState.Declined: return new Release1ChiefDecision(release1ChiefRecord with { State = Release1ChiefState.DemandOpen, DemandRound = Math.Min(record.DemandRound + 1, 3), LastShortfallNoticed = null }, Release1ChiefAction.OpenDemand); default: return new Release1ChiefDecision(record, Release1ChiefAction.None); } } if (record.State == Release1ChiefState.Adopted) { release1ChiefRecord = release1ChiefRecord with { State = Release1ChiefState.DemandOpen, DemandRound = 1 }; } if (!IsUnpaid(release1ChiefRecord)) { return new Release1ChiefDecision(record, Release1ChiefAction.None); } if (observed != Release1ChiefObservedEvent.RoseToWatched) { return new Release1ChiefDecision(release1ChiefRecord with { LockdownAnnouncements = release1ChiefRecord.LockdownAnnouncements + 1 }, Release1ChiefAction.AnnounceLockdown); } return new Release1ChiefDecision(release1ChiefRecord with { WatchLinesSent = release1ChiefRecord.WatchLinesSent + 1 }, Release1ChiefAction.RecordWatchLine); } public static Release1ChiefDecision Decline(Release1ChiefRecord record) { ArgumentNullException.ThrowIfNull(record, "record"); if (record.State == Release1ChiefState.DemandOpen) { return new Release1ChiefDecision(record with { State = Release1ChiefState.Declined, DeclineRepliesSent = record.DeclineRepliesSent + 1, Revision = record.Revision + 1 }, Release1ChiefAction.ReplyDeclined); } return new Release1ChiefDecision(record, Release1ChiefAction.None); } public static Release1ChiefDecision AbandonUnappliedPayment(Release1ChiefRecord record) { ArgumentNullException.ThrowIfNull(record, "record"); if (record.State == Release1ChiefState.Paying) { return new Release1ChiefDecision(record with { State = Release1ChiefState.DemandOpen, Revision = record.Revision + 1 }, Release1ChiefAction.None); } return new Release1ChiefDecision(record, Release1ChiefAction.None); } public static Release1ChiefDecision Settle(Release1ChiefRecord record) { ArgumentNullException.ThrowIfNull(record, "record"); if (record.State != Release1ChiefState.Paying) { return new Release1ChiefDecision(record, Release1ChiefAction.None); } Release1ChiefRecord release1ChiefRecord = record with { State = Release1ChiefState.Settled, Revision = record.Revision + 1 }; if (!record.LockdownEngaged) { return new Release1ChiefDecision(release1ChiefRecord, Release1ChiefAction.None); } return new Release1ChiefDecision(release1ChiefRecord with { LockdownEngaged = false, PaidLifts = record.PaidLifts + 1 }, Release1ChiefAction.LiftPaid); } public static Release1ChiefDecision CoolBelowWatched(Release1ChiefRecord record) { ArgumentNullException.ThrowIfNull(record, "record"); if (record.LockdownEngaged) { return new Release1ChiefDecision(record with { LockdownEngaged = false, CooledLifts = record.CooledLifts + 1, Revision = record.Revision + 1 }, Release1ChiefAction.LiftCooled); } return new Release1ChiefDecision(record, Release1ChiefAction.None); } } public static class Release1ChiefEffect { public const string Scope = "chief-campbell"; public const string WalletSource = "player-wallet"; public const string Kind = "CashTransfer"; public static string EffectId(int round, long revision) { return $"chief-campbell-cash-v1-r{round}-c{revision}"; } public static string NativeReceiptId(int round, long revision) { return $"chief-campbell-cash-native-v1-r{round}-c{revision}"; } } public static class Release1ChiefPresentation { public static Release1PresentationPlan BuildPlan(Release1StoryState? state) { Release1ChiefRecord release1ChiefRecord = state?.ChiefRecord; if ((object)release1ChiefRecord == null) { return Release1PresentationPlan.Empty; } string playerId = state.PlayerId; List list = new List(); if (release1ChiefRecord.State == Release1ChiefState.Settled) { list.Add(Message(playerId, release1ChiefRecord, PaidReceipt(release1ChiefRecord.DemandRound, release1ChiefRecord.Revision), "Received. The file is shredded, your name is off the board, and my officers have no reason to know you. Do not give them one.")); } if (release1ChiefRecord.DeclineRepliesSent >= 1) { list.Add(CounterMessage(playerId, DeclineReceipt(release1ChiefRecord.DeclineRepliesSent), "Your choice. The file stays where it is. It gets thicker every time my officers pick you up, and the number goes up with it.")); } if (release1ChiefRecord.WatchLinesSent >= 1) { list.Add(CounterMessage(playerId, WatchReceipt(release1ChiefRecord.WatchLinesSent), "Chief Campbell. You are on the watch list now. My officers are told to look at you twice. That is all it is, for now.")); } if (release1ChiefRecord.LockdownAnnouncements >= 1) { list.Add(CounterMessage(playerId, LockdownReceipt(release1ChiefRecord.LockdownAnnouncements), "Chief Campbell. The town goes to curfew tonight and stays there, and it is because of you. Every hour is curfew and my officers will arrest you on sight. Pay what you owe, or stay quiet long enough for this to cool.")); } if (release1ChiefRecord.PaidLifts >= 1) { list.Add(CounterMessage(playerId, LiftPaidReceipt(release1ChiefRecord.PaidLifts), "Curfew is lifted. Consider the account settled.")); } if (release1ChiefRecord.CooledLifts >= 1) { list.Add(CounterMessage(playerId, LiftCooledReceipt(release1ChiefRecord.CooledLifts), "Curfew is lifted. You went quiet long enough. The file is still on my desk.")); } if (release1ChiefRecord.PaymentBlockedNotices >= 1) { list.Add(CounterMessage(playerId, PaymentBlockedReceipt(release1ChiefRecord.PaymentBlockedNotices), "Your payment could not be confirmed. The demand still stands.")); } double? lastShortfallNoticed = release1ChiefRecord.LastShortfallNoticed; if (lastShortfallNoticed.HasValue) { double valueOrDefault = lastShortfallNoticed.GetValueOrDefault(); list.Add(Message(playerId, release1ChiefRecord, ShortfallReceipt(valueOrDefault), Release1ChiefCampbellCopy.ShortCashText(release1ChiefRecord.Demand))); } Release1DesiredDecision decision = ((release1ChiefRecord.State == Release1ChiefState.DemandOpen) ? new Release1DesiredDecision(DemandDecisionId(release1ChiefRecord.DemandRound), Copy(Release1ChiefCampbellCopy.DemandText(release1ChiefRecord.DemandRound)), new Release1DecisionOption[2] { new Release1DecisionOption(Copy(Release1ChiefCampbellCopy.PayLabel(release1ChiefRecord.Demand)), Release1PresentationCommand.ChiefCampbellPay), new Release1DecisionOption(Copy("Not today"), Release1PresentationCommand.ChiefCampbellDecline) }) : null); return new Release1PresentationPlan(list, decision, Array.Empty()); } public static string LockdownAnnouncementCorrelation(string playerId, Release1ChiefRecord record) { return CounterCorrelation(playerId, LockdownReceipt(record.LockdownAnnouncements)); } private static Release1DesiredMessage Message(string playerId, Release1ChiefRecord record, string receiptId, string text) { return new Release1DesiredMessage(Correlation(playerId, record.DemandRound, receiptId), Copy(text)); } private static Release1DesiredMessage CounterMessage(string playerId, string receiptId, string text) { return new Release1DesiredMessage(CounterCorrelation(playerId, receiptId), Copy(text)); } private static string CounterCorrelation(string playerId, string receiptId) { return Correlation(playerId, 1, receiptId); } private static string Correlation(string playerId, int round, string receiptId) { return Release1LogicalCorrelation.Create(playerId, "release1.chief-campbell", Math.Max(round, 1), Release1TransitionKind.MissionOffered, receiptId).Value; } private static string DemandDecisionId(int round) { return $"chief-campbell-demand-r{round}"; } private static string DeclineReceipt(int count) { return $"chief-decline-{count}"; } private static string PaidReceipt(int round, long revision) { return $"chief-paid-r{round}-c{revision}"; } private static string WatchReceipt(int count) { return $"chief-watch-{count}"; } private static string LockdownReceipt(int count) { return $"chief-lockdown-{count}"; } private static string LiftPaidReceipt(int count) { return $"chief-lift-paid-{count}"; } private static string LiftCooledReceipt(int count) { return $"chief-lift-cooled-{count}"; } private static string PaymentBlockedReceipt(int count) { return $"chief-payment-blocked-{count}"; } private static string ShortfallReceipt(double shortfall) { return "chief-short-" + ((long)Math.Round(shortfall)).ToString(CultureInfo.InvariantCulture); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1ChiefService : IDisposable { private const double CashReadTolerance = 0.01; private const string ChiefCashEffectIdPrefix = "chief-campbell-cash-v1-"; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1ChiefTierObserver _observer; private readonly Release1ChiefLedgerReader _readLedger; private readonly Release1ChiefLedgerWiper _wipeLedger; private readonly Release1ChiefEpochReader _epochs; private readonly LocalPressureProfile _profile; private readonly Release1ConvergenceThrottle _throttle = new Release1ConvergenceThrottle(); private readonly HashSet _reportedHolds = new HashSet(StringComparer.Ordinal); private readonly HashSet _confirmedDebits = new HashSet(StringComparer.Ordinal); private readonly HashSet _wipedEffectIds = new HashSet(StringComparer.Ordinal); private bool _disposed; public Release1ChiefService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1ChiefTierObserver observer, Release1ChiefLedgerReader readLedger, Release1ChiefLedgerWiper wipeLedger, Release1ChiefEpochReader epochs, LocalPressureProfile profile) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _observer = observer ?? throw new ArgumentNullException("observer"); _readLedger = readLedger ?? throw new ArgumentNullException("readLedger"); _wipeLedger = wipeLedger ?? throw new ArgumentNullException("wipeLedger"); _epochs = epochs ?? throw new ArgumentNullException("epochs"); _profile = profile ?? throw new ArgumentNullException("profile"); } public void OnPreLoad() { if (!_disposed) { _throttle.Clear(); _reportedHolds.Clear(); _confirmedDebits.Clear(); _wipedEffectIds.Clear(); } } public void OnLoadComplete() { if (!_disposed) { _throttle.Clear(); _reportedHolds.Clear(); _confirmedDebits.Clear(); _wipedEffectIds.Clear(); if (_observer.DroppedCount > 0) { OrganizedCrimeLog.Warning($"Chief Campbell's tier event queue dropped {_observer.DroppedCount} event(s) since load; the queue is bounded and the oldest were discarded."); } } } public void OnSaveStart() { if (!_disposed) { _throttle.Clear(); } } public void OnSaveComplete() { if (!_disposed) { _throttle.Clear(); } } public void Update() { if (_disposed || !_story.TryGetActiveContext(out var snapshot, out var _) || _story.Phase != Release1StoryRuntimePhase.Active) { return; } Release1StoryState state = _story.State; if ((object)state == null) { return; } double totalMinutes; double? gameMinutes = ((_world.TryReadCanonicalTotalMinutes(out totalMinutes) == Release1SmallCourtesyWorldReadStatus.Ready) ? new double?(totalMinutes) : ((double?)null)); if (!_throttle.TryBegin(gameMinutes, state.Revision) || !_epochs(out var sessionEpoch, out var loadEpoch)) { return; } LocalPressureState localPressureState = _readLedger(snapshot.PlayerId); if ((object)localPressureState != null) { Release1ChiefRecord chiefRecord = state.ChiefRecord; if ((object)chiefRecord == null) { Adopt(localPressureState); } else if (!ContinuePayment(chiefRecord, snapshot.PlayerId, localPressureState)) { CommitAppliedChiefCashEffects(); DrainOne(chiefRecord, snapshot.PlayerId, sessionEpoch, loadEpoch); ReconcileLockdown(_story.State?.ChiefRecord, localPressureState); } } } public bool TryPay() { if (_disposed) { return false; } _throttle.Clear(); return RunPay(); } public bool TryDecline() { if (_disposed) { return false; } _throttle.Clear(); if (!_story.TryGetActiveContext(out var _, out var _)) { return false; } Release1ChiefRecord release1ChiefRecord = _story.State?.ChiefRecord; if ((object)release1ChiefRecord == null) { return false; } Release1ChiefDecision release1ChiefDecision = Release1ChiefLedger.Decline(release1ChiefRecord); if (release1ChiefDecision.Action != Release1ChiefAction.ReplyDeclined) { return false; } return _story.TrySetChiefRecord(release1ChiefDecision.Next).Accepted; } public void Dispose() { _disposed = true; } private bool RunPay() { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return false; } Release1StoryState state = _story.State; Release1ChiefRecord release1ChiefRecord = state?.ChiefRecord; if ((object)release1ChiefRecord == null || release1ChiefRecord.State != Release1ChiefState.DemandOpen) { return false; } if (_world.TryReadCashBalance(out var balance) != Release1SmallCourtesyWorldReadStatus.Ready) { return false; } int demand = release1ChiefRecord.Demand; if (balance < (float)demand) { double num = (double)demand - (double)balance; if (release1ChiefRecord.LastShortfallNoticed != num) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetChiefRecord(release1ChiefRecord with { LastShortfallNoticed = num, Revision = release1ChiefRecord.Revision + 1 }); if (!release1StoryRuntimeCommandResult.Accepted) { OrganizedCrimeLog.Warning($"Chief shortfall notice for round {release1ChiefRecord.DemandRound} was rejected: {release1StoryRuntimeCommandResult.RejectReason}: {release1StoryRuntimeCommandResult.Message}"); } } return false; } int demandRound = release1ChiefRecord.DemandRound; string receiptId = $"chief-pay-r{demandRound}-c{release1ChiefRecord.Revision}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.chief-campbell", demandRound, Release1TransitionKind.ChiefPaymentAccepted, receiptId).Value; string text = Release1ChiefEffect.EffectId(demandRound, release1ChiefRecord.Revision); Release1NativeEffectJournalEntry effect = new Release1NativeEffectJournalEntry(text, "release1.chief-campbell", demandRound, "CashTransfer", "player-wallet", "chief-campbell", Release1ChiefCampbellCopy.Dollars(demand), Release1NativeEffectPhase.Prepared, null, state.Revision + 1, null, value, ExecutionBlocked: false, release1ChiefRecord.Revision); if (!_story.TryPrepareNativeEffect(effect).Accepted) { return false; } Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryDebitCashBalance(0f - (float)demand); } catch { release1SmallCourtesyWorldMutationStatus = Release1SmallCourtesyWorldMutationStatus.Ambiguous; } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { OrganizedCrimeLog.Warning($"Chief payment debit for round {demandRound} did not succeed ({release1SmallCourtesyWorldMutationStatus}); blocking the effect, no cash confirmed moved."); BlockEffect(text, "initial", release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Unavailable); if (release1SmallCourtesyWorldMutationStatus == Release1SmallCourtesyWorldMutationStatus.Unavailable) { Release1ChiefRecord release1ChiefRecord2 = _story.State?.ChiefRecord; if ((object)release1ChiefRecord2 != null) { Release1ChiefDecision release1ChiefDecision = Release1ChiefLedger.AbandonUnappliedPayment(release1ChiefRecord2); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = _story.TrySetChiefRecord(release1ChiefDecision.Next); if (!release1StoryRuntimeCommandResult2.Accepted) { OrganizedCrimeLog.Warning($"Chief demand reopen after an unavailable debit was itself rejected: {release1StoryRuntimeCommandResult2.RejectReason}: {release1StoryRuntimeCommandResult2.Message}"); } } } return false; } _confirmedDebits.Add(text); if (_world.TryReadCashBalance(out var balance2) != Release1SmallCourtesyWorldReadStatus.Ready || (double)Math.Abs(balance2 - (balance - (float)demand)) > 0.01) { OrganizedCrimeLog.Warning($"Chief payment read-back for round {demandRound} did not confirm the debit; the payment will retry to complete."); return false; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult3 = _story.TryMarkNativeEffectApplied(text, Release1ChiefEffect.NativeReceiptId(demandRound, release1ChiefRecord.Revision), Release1NativeEffectPersistenceMode.RevertTolerant); if (!release1StoryRuntimeCommandResult3.Accepted) { OrganizedCrimeLog.Warning($"Chief payment for round {demandRound} could not be marked applied: {release1StoryRuntimeCommandResult3.RejectReason}: {release1StoryRuntimeCommandResult3.Message}; the payment will retry to complete."); } return release1StoryRuntimeCommandResult3.Accepted; } private bool ContinuePayment(Release1ChiefRecord record, string playerId, LocalPressureState ledger) { if (record.State != Release1ChiefState.Paying) { return false; } object obj; if (record.NativeEffectIds.Count <= 0) { obj = null; } else { IReadOnlyList nativeEffectIds = record.NativeEffectIds; obj = nativeEffectIds[nativeEffectIds.Count - 1]; } string effectId = (string)obj; if (effectId == null) { return false; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State?.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effectId); if ((object)release1NativeEffectJournalEntry == null) { return false; } if (release1NativeEffectJournalEntry.ExecutionBlocked) { ReconcileLockdown(_story.State?.ChiefRecord, ledger); return true; } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { if (!_confirmedDebits.Contains(release1NativeEffectJournalEntry.EffectId)) { return RetryUnrecordedBlock(release1NativeEffectJournalEntry.EffectId); } return RetryUnconfirmedPayment(record, release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Applied) { return false; } if (!_wipedEffectIds.Contains(effectId)) { if (!_wipeLedger(playerId).Accepted) { return true; } _wipedEffectIds.Add(effectId); } Release1ChiefDecision release1ChiefDecision = Release1ChiefLedger.Settle(record); if (release1ChiefDecision.Action == Release1ChiefAction.LiftPaid && _world.TryReleaseLockdown(out string reason) != Release1SmallCourtesyWorldMutationStatus.Succeeded) { ReportHoldOnce(reason); return true; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetChiefRecord(release1ChiefDecision.Next); if (!release1StoryRuntimeCommandResult.Accepted) { OrganizedCrimeLog.Warning($"Chief settle write for round {record.DemandRound} was rejected: {release1StoryRuntimeCommandResult.RejectReason}: {release1StoryRuntimeCommandResult.Message}"); } return true; } private void CommitAppliedChiefCashEffects() { Release1StoryState state = _story.State; if ((object)state == null) { return; } long revision = state.Revision; if (revision > _story.LastPersistedRevision) { return; } foreach (Release1NativeEffectJournalEntry nativeEffect in state.NativeEffects) { if (nativeEffect.Phase == Release1NativeEffectPhase.Applied && !nativeEffect.ExecutionBlocked && nativeEffect.AuthorizedStoryCorrelationId != null && nativeEffect.EffectId.StartsWith("chief-campbell-cash-v1-", StringComparison.Ordinal)) { _story.TryCommitNativeEffect(nativeEffect.EffectId, nativeEffect.AuthorizedStoryCorrelationId, revision); } } } private bool RetryUnrecordedBlock(string effectId) { BlockEffect(effectId, "retry", notifyPlayer: true); return true; } private void BlockEffect(string effectId, string attempt, bool notifyPlayer) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryRecordNativeEffectIssuance(effectId, Release1NativeEffectIssuanceOutcome.Ambiguous); if (!release1StoryRuntimeCommandResult.Accepted) { OrganizedCrimeLog.Warning($"Chief payment block ({attempt}) for effect {effectId} was itself refused: {release1StoryRuntimeCommandResult.RejectReason}: {release1StoryRuntimeCommandResult.Message}; holding for a later pass."); } else { if (!notifyPlayer || release1StoryRuntimeCommandResult.Status != Release1StoryCommandStatus.Accepted) { return; } Release1ChiefRecord release1ChiefRecord = _story.State?.ChiefRecord; if ((object)release1ChiefRecord != null) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = _story.TrySetChiefRecord(release1ChiefRecord with { PaymentBlockedNotices = release1ChiefRecord.PaymentBlockedNotices + 1, Revision = release1ChiefRecord.Revision + 1 }); if (!release1StoryRuntimeCommandResult2.Accepted) { OrganizedCrimeLog.Warning($"Chief payment blocked notice could not be recorded: {release1StoryRuntimeCommandResult2.RejectReason}: {release1StoryRuntimeCommandResult2.Message}"); } } } } private bool RetryUnconfirmedPayment(Release1ChiefRecord record, Release1NativeEffectJournalEntry effect) { if (_world.TryReadCashBalance(out var _) != Release1SmallCourtesyWorldReadStatus.Ready) { OrganizedCrimeLog.Warning($"Chief payment retry for round {record.DemandRound} could not read the wallet back; holding for a later pass."); return true; } string nativeReceiptId = Release1ChiefEffect.NativeReceiptId(record.DemandRound, record.Revision - 1); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryMarkNativeEffectApplied(effect.EffectId, nativeReceiptId, Release1NativeEffectPersistenceMode.RevertTolerant); if (!release1StoryRuntimeCommandResult.Accepted) { OrganizedCrimeLog.Warning($"Chief payment retry for round {record.DemandRound} could not mark the effect applied: {release1StoryRuntimeCommandResult.RejectReason}: {release1StoryRuntimeCommandResult.Message}; holding for a later pass."); } return true; } private void Adopt(LocalPressureState ledger) { LocalPressureTier tier = LocalPressureTransitions.GetTier(ledger.LocalHeat, _profile); if (_story.TrySetChiefRecord(Release1ChiefRecord.Adopt(tier, ledger.KnownOffender)).Accepted) { OrganizedCrimeLog.Receipt($"Chief Campbell record adopted: tier {tier}, Known Offender {ledger.KnownOffender}."); } } private void DrainOne(Release1ChiefRecord record, string playerId, Guid sessionEpoch, long loadEpoch) { if (!_observer.TryDequeue(playerId, sessionEpoch, loadEpoch, out Release1ChiefQueuedEvent queued)) { return; } Release1ChiefDecision release1ChiefDecision = Release1ChiefLedger.Observe(record, queued.Kind); if (release1ChiefDecision.Next.Revision != record.Revision) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetChiefRecord(release1ChiefDecision.Next); if (!release1StoryRuntimeCommandResult.Accepted) { OrganizedCrimeLog.Warning($"Chief record write was rejected on {queued.Kind}: {release1StoryRuntimeCommandResult.RejectReason}: {release1StoryRuntimeCommandResult.Message}"); } } } private void ReconcileLockdown(Release1ChiefRecord? record, LocalPressureState ledger) { if ((object)record == null) { return; } LocalPressureTier tier = LocalPressureTransitions.GetTier(ledger.LocalHeat, _profile); if (record.LockdownEngaged) { if (tier >= LocalPressureTier.Watched) { if (!TryEngageLockdownOnce(out string reason)) { ReportHoldOnce(reason); } return; } Release1ChiefDecision release1ChiefDecision = Release1ChiefLedger.CoolBelowWatched(record); if (release1ChiefDecision.Action != Release1ChiefAction.LiftCooled) { return; } if (_world.TryReleaseLockdown(out string reason2) != Release1SmallCourtesyWorldMutationStatus.Succeeded) { ReportHoldOnce(reason2); return; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetChiefRecord(release1ChiefDecision.Next); if (!release1StoryRuntimeCommandResult.Accepted) { OrganizedCrimeLog.Warning($"Chief cooled-lift write was rejected: {release1StoryRuntimeCommandResult.RejectReason}: {release1StoryRuntimeCommandResult.Message}"); } } else { if (tier < LocalPressureTier.Critical || _observer.QueueDepth > 0 || record.LockdownAnnouncements < 1 || !Release1ChiefLedger.IsUnpaid(record) || !HasAnnouncementReceipt(record)) { return; } if (!TryEngageLockdownOnce(out string reason3)) { ReportHoldOnce(reason3); return; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = _story.TrySetChiefRecord(record with { LockdownEngaged = true, Revision = record.Revision + 1 }); if (!release1StoryRuntimeCommandResult2.Accepted) { OrganizedCrimeLog.Warning($"Chief lockdown engage write was rejected: {release1StoryRuntimeCommandResult2.RejectReason}: {release1StoryRuntimeCommandResult2.Message}"); } } } private bool TryEngageLockdownOnce(out string reason) { return _world.TryEngageLockdown(out reason) == Release1SmallCourtesyWorldMutationStatus.Succeeded; } private bool HasAnnouncementReceipt(Release1ChiefRecord record) { Release1StoryState state = _story.State; if ((object)state == null) { return false; } string correlation = Release1ChiefPresentation.LockdownAnnouncementCorrelation(state.PlayerId, record); return state.PresentationReceipts.Any((Release1PresentationReceipt receipt) => receipt.CorrelationId == correlation); } private void ReportHoldOnce(string message) { if (!string.IsNullOrEmpty(message) && _reportedHolds.Add(message)) { OrganizedCrimeLog.Warning("Chief Campbell lockdown gate is holding: " + message); } } } public enum Release1ChiefObservedEvent { ArrestObserved, RoseToWatched, RoseToCritical } public sealed record Release1ChiefQueuedEvent(Release1ChiefObservedEvent Kind, string PlayerId, Guid SessionEpoch, long LoadEpoch); public sealed class Release1ChiefTierObserver : ILocalPressureTierTransitionSink { public const int MaximumQueueDepth = 32; private readonly object _gate = new object(); private readonly Queue _queue = new Queue(); public int QueueDepth { get { lock (_gate) { return _queue.Count; } } } public int DroppedCount { get; private set; } public void Publish(LocalPressureTierTransitionNotification notification) { if ((object)notification == null) { return; } lock (_gate) { Enqueue(notification, Release1ChiefObservedEvent.ArrestObserved); if (notification.CurrentTier > notification.PreviousTier) { if (notification.CurrentTier == LocalPressureTier.Watched) { Enqueue(notification, Release1ChiefObservedEvent.RoseToWatched); } else if (notification.CurrentTier == LocalPressureTier.Critical) { Enqueue(notification, Release1ChiefObservedEvent.RoseToCritical); } } } } public void ResetForEpoch(Guid sessionEpoch, long loadEpoch) { Clear(); } public bool TryDequeue(string playerId, Guid sessionEpoch, long loadEpoch, out Release1ChiefQueuedEvent queued) { queued = null; lock (_gate) { if (_queue.Count == 0) { return false; } Release1ChiefQueuedEvent release1ChiefQueuedEvent = _queue.Peek(); if (!string.Equals(release1ChiefQueuedEvent.PlayerId, playerId, StringComparison.Ordinal) || release1ChiefQueuedEvent.SessionEpoch != sessionEpoch || release1ChiefQueuedEvent.LoadEpoch != loadEpoch) { return false; } queued = _queue.Dequeue(); return true; } } public void Clear() { lock (_gate) { _queue.Clear(); } } private void Enqueue(LocalPressureTierTransitionNotification notification, Release1ChiefObservedEvent kind) { while (_queue.Count >= 32) { _queue.Dequeue(); DroppedCount++; } _queue.Enqueue(new Release1ChiefQueuedEvent(kind, notification.PlayerId, notification.SessionEpoch, notification.LoadEpoch)); } } public enum Release1WindowObservation { Unreadable, Clean, Breached } public enum Release1WindowHealPolicy { HealBeforeElapse, ElapseBeforeHeal } public enum Release1WindowDecision { Hold, OpenWindow, ClearBreach, RecordBreach, Elapsed, Fail, None } public sealed record Release1WindowProgress(double? OpenedAtGameMinutes, double? BreachSinceGameMinutes, bool Satisfied); public sealed record Release1WindowProfile(double WindowGameMinutes, double GraceGameMinutes, Release1WindowHealPolicy HealPolicy); public static class Release1ConditionWindow { public static Release1WindowDecision Decide(Release1WindowObservation observation, Release1WindowProgress progress, double nowGameMinutes, Release1WindowProfile profile) { ArgumentNullException.ThrowIfNull(progress, "progress"); ArgumentNullException.ThrowIfNull(profile, "profile"); if (!double.IsFinite(nowGameMinutes) || nowGameMinutes < 0.0 || !double.IsFinite(profile.WindowGameMinutes) || profile.WindowGameMinutes <= 0.0 || !double.IsFinite(profile.GraceGameMinutes) || profile.GraceGameMinutes <= 0.0) { return Release1WindowDecision.Hold; } switch (observation) { case Release1WindowObservation.Unreadable: return Release1WindowDecision.Hold; case Release1WindowObservation.Clean: { double? breachSinceGameMinutes = progress.OpenedAtGameMinutes; if (breachSinceGameMinutes.HasValue) { double valueOrDefault2 = breachSinceGameMinutes.GetValueOrDefault(); if (profile.HealPolicy == Release1WindowHealPolicy.HealBeforeElapse && progress.BreachSinceGameMinutes.HasValue) { return Release1WindowDecision.ClearBreach; } if (!progress.Satisfied && nowGameMinutes >= valueOrDefault2 + profile.WindowGameMinutes) { return Release1WindowDecision.Elapsed; } if (progress.BreachSinceGameMinutes.HasValue) { return Release1WindowDecision.ClearBreach; } return Release1WindowDecision.None; } return Release1WindowDecision.OpenWindow; } default: { if (!progress.OpenedAtGameMinutes.HasValue) { return Release1WindowDecision.None; } if (progress.Satisfied) { return Release1WindowDecision.None; } double? breachSinceGameMinutes = progress.BreachSinceGameMinutes; if (breachSinceGameMinutes.HasValue) { double valueOrDefault = breachSinceGameMinutes.GetValueOrDefault(); if (!(nowGameMinutes >= valueOrDefault + profile.GraceGameMinutes)) { return Release1WindowDecision.Hold; } return Release1WindowDecision.Fail; } return Release1WindowDecision.RecordBreach; } } } } public sealed record Release1WindowCopy(string? ConfirmedText, string? BreachedText) { public static Release1WindowCopy None { get; } = new Release1WindowCopy(null, null); public void Validate() { ValidateSlot(ConfirmedText, "ConfirmedText"); ValidateSlot(BreachedText, "BreachedText"); } private static void ValidateSlot(string? value, string parameterName) { if (value == null) { return; } if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("A copy slot that exists cannot be empty.", parameterName); } if (!string.Equals(Release1PlayerCopy.Normalize(value), value, StringComparison.Ordinal)) { throw new ArgumentException("Player copy must already be normalized.", parameterName); } if (value.Contains("--", StringComparison.Ordinal)) { throw new ArgumentException("Player copy cannot carry a double hyphen.", parameterName); } char[] array = new char[3] { '‘', '’', '%' }; foreach (char value2 in array) { if (value.Contains(value2)) { throw new ArgumentException("Player copy carries a banned character.", parameterName); } } } [CompilerGenerated] private Release1WindowCopy(Release1WindowCopy original) { ConfirmedText = original.ConfirmedText; BreachedText = original.BreachedText; } } public sealed class Release1ConvergenceThrottle { private double? _lastGameMinute; private long _lastRevision = -1L; public bool TryBegin(double? gameMinutes, long revision) { if (gameMinutes.HasValue) { double valueOrDefault = gameMinutes.GetValueOrDefault(); double num = Math.Floor(valueOrDefault); double? lastGameMinute = _lastGameMinute; if (lastGameMinute.HasValue && _lastGameMinute.Value == num && _lastRevision == revision) { return false; } _lastGameMinute = num; _lastRevision = revision; return true; } _lastGameMinute = null; _lastRevision = revision; return true; } public void Clear() { _lastGameMinute = null; } } public enum Release1DecisionPromptInput { None, Open, Primary, Secondary, Close } public enum Release1CursorLockState { None, Locked, Confined } public sealed record Release1DecisionPromptCard { public string Id { get; } public string PassiveText { get; } public string Title { get; } public string Body { get; } public string PrimaryLabel { get; } public Action PrimaryAction { get; } public string? SecondaryLabel { get; } public Action? SecondaryAction { get; } public Release1DecisionPromptCard(string id, string passiveText, string title, string body, string primaryLabel, Action primaryAction, string? secondaryLabel = null, Action? secondaryAction = null) { if (string.IsNullOrWhiteSpace(id)) { throw new ArgumentException("Prompt id is required.", "id"); } if (string.IsNullOrWhiteSpace(passiveText)) { throw new ArgumentException("Passive text is required.", "passiveText"); } if (string.IsNullOrWhiteSpace(title)) { throw new ArgumentException("Title is required.", "title"); } if (string.IsNullOrWhiteSpace(body)) { throw new ArgumentException("Body is required.", "body"); } if (string.IsNullOrWhiteSpace(primaryLabel)) { throw new ArgumentException("Primary label is required.", "primaryLabel"); } ArgumentNullException.ThrowIfNull(primaryAction, "primaryAction"); if (secondaryLabel == null != (secondaryAction == null)) { throw new ArgumentException("Secondary label and action must either both be present or both be absent."); } Id = id; PassiveText = passiveText; Title = title; Body = body; PrimaryLabel = primaryLabel; PrimaryAction = primaryAction; SecondaryLabel = secondaryLabel; SecondaryAction = secondaryAction; } } public interface IRelease1DecisionPromptPlatform { bool CameraAvailable { get; } bool VanillaUiOwnsCursor { get; } Release1CursorLockState CursorLockState { get; } bool CursorVisible { get; } string OpenKeyLabel { get; } Release1DecisionPromptInput ReadInput(bool modalOpen); void AddActiveUiElement(string token); void RemoveActiveUiElement(string token); void SetCanLook(bool canLook); void FreeMouse(); void LockMouse(); void SetCursor(Release1CursorLockState lockState, bool visible); } public static class Release1PlayerCopy { public const string FamilyName = "the Cheeky Blinders"; public static string Normalize(string value) { ArgumentNullException.ThrowIfNull(value, "value"); return value.Replace(" — ", " - ", StringComparison.Ordinal).Replace(" – ", " - ", StringComparison.Ordinal).Replace("—", "-", StringComparison.Ordinal) .Replace("–", "-", StringComparison.Ordinal); } public static string? AttemptHeader(TMode mode, int attempt) where TMode : struct, Enum { return mode.ToString() switch { "Primary" => null, "MakeGood" => $"Make good, attempt {attempt}", "Recovery" => $"Recovery, attempt {attempt}", _ => throw new ArgumentOutOfRangeException("mode", mode, "Unrecognized attempt mode."), }; } public static string Deadline(double? hours) { if (!hours.HasValue) { return "none"; } if (hours.Value == 24.0) { return "one day"; } return FormatHours(hours.Value) + " hours"; } public static string JoinLines(params string?[] lines) { return string.Join("\n", lines.Where((string line) => line != null)); } private static string FormatHours(double hours) { if (hours != Math.Floor(hours)) { return hours.ToString("0.##", CultureInfo.InvariantCulture); } return ((long)hours).ToString(CultureInfo.InvariantCulture); } } public sealed class Release1DecisionPromptHost : IDisposable { public const string UiToken = "OrganizedCrime.DecisionPrompt"; private readonly Func _cardProvider; private readonly IRelease1DecisionPromptPlatform _platform; private readonly Action? _log; private Release1DecisionPromptCard? _activeCard; private Release1CursorLockState _previousLockState; private bool _previousCursorVisible; private bool _cameraCaptured; private bool _commandIssuedThisFrame; private bool _disposed; public bool ModalOpen { get; private set; } public Release1DecisionPromptHost(Func cardProvider, IRelease1DecisionPromptPlatform platform, Action? log = null) { _cardProvider = cardProvider ?? throw new ArgumentNullException("cardProvider"); _platform = platform ?? throw new ArgumentNullException("platform"); _log = log; } public void Update() { if (_disposed) { return; } _commandIssuedThisFrame = false; Release1DecisionPromptCard release1DecisionPromptCard = SafeGetCard(); if (ModalOpen && (object)release1DecisionPromptCard == null) { CloseModal(restoreGameplay: true); } else if (ModalOpen && _platform.VanillaUiOwnsCursor) { CloseModal(restoreGameplay: false); } else { if (!ModalOpen && _platform.VanillaUiOwnsCursor) { return; } Release1DecisionPromptInput release1DecisionPromptInput = _platform.ReadInput(ModalOpen); if (!ModalOpen) { if ((object)release1DecisionPromptCard != null && release1DecisionPromptInput == Release1DecisionPromptInput.Open) { OpenModal(release1DecisionPromptCard); } return; } _activeCard = release1DecisionPromptCard; switch (release1DecisionPromptInput) { case Release1DecisionPromptInput.Close: CloseModal(restoreGameplay: true); return; case Release1DecisionPromptInput.Primary: Execute(_activeCard.PrimaryAction); break; case Release1DecisionPromptInput.Secondary: if (_activeCard.SecondaryAction != null) { Execute(_activeCard.SecondaryAction); } break; } if (ModalOpen) { KeepMouseFree(); } } } public void OnGUI() { if (_disposed) { return; } Release1DecisionPromptCard release1DecisionPromptCard = (ModalOpen ? _activeCard : SafeGetCard()); if ((object)release1DecisionPromptCard != null) { if (!ModalOpen) { DrawPassive(release1DecisionPromptCard); } else { DrawModal(release1DecisionPromptCard); } } } public void OnPreLoad() { CloseModal(restoreGameplay: true); } public void OnPreSceneChange() { CloseModal(restoreGameplay: true); } public void Dispose() { if (!_disposed) { CloseModal(restoreGameplay: true); _disposed = true; } } private Release1DecisionPromptCard? SafeGetCard() { try { return _cardProvider(); } catch (Exception ex) { _log?.Invoke("Decision prompt view failed: " + ex.Message); if (ModalOpen) { CloseModal(restoreGameplay: true); } return null; } } private void OpenModal(Release1DecisionPromptCard card) { if (!ModalOpen && !_disposed) { _activeCard = card; _previousLockState = _platform.CursorLockState; _previousCursorVisible = _platform.CursorVisible; _cameraCaptured = _platform.CameraAvailable; if (_cameraCaptured) { _platform.AddActiveUiElement("OrganizedCrime.DecisionPrompt"); _platform.SetCanLook(canLook: false); _platform.FreeMouse(); } _platform.SetCursor(Release1CursorLockState.None, visible: true); ModalOpen = true; } } private void KeepMouseFree() { if (_cameraCaptured) { _platform.FreeMouse(); } _platform.SetCursor(Release1CursorLockState.None, visible: true); } private void CloseModal(bool restoreGameplay) { if (!ModalOpen) { return; } ModalOpen = false; _activeCard = null; if (_cameraCaptured) { _platform.RemoveActiveUiElement("OrganizedCrime.DecisionPrompt"); _platform.SetCanLook(canLook: true); if (restoreGameplay && _previousLockState == Release1CursorLockState.Locked) { _platform.LockMouse(); } } if (restoreGameplay) { _platform.SetCursor(_previousLockState, _previousCursorVisible); } _cameraCaptured = false; } private void Execute(Action action) { if (_commandIssuedThisFrame || !ModalOpen) { return; } _commandIssuedThisFrame = true; try { action(); Release1DecisionPromptCard release1DecisionPromptCard = SafeGetCard(); if ((object)release1DecisionPromptCard == null) { CloseModal(restoreGameplay: true); } else { _activeCard = release1DecisionPromptCard; } } catch (Exception ex) { _log?.Invoke("Decision prompt action failed: " + ex.Message); CloseModal(restoreGameplay: true); } } private void DrawPassive(Release1DecisionPromptCard card) { //IL_003a: 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) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005d: 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_0070: Expected O, but got Unknown //IL_0071: Expected O, but got Unknown //IL_0077: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(520f, Mathf.Max(320f, (float)Screen.width - 32f)); Rect val = new Rect((float)Screen.width - num - 24f, 42f, num, 88f); GUIStyle val2 = new GUIStyle(GUI.skin.box) { fontSize = 18, wordWrap = true, padding = new RectOffset(18, 18, 12, 12) }; val2.normal.textColor = Color.white; GUI.Box(val, Release1PlayerCopy.Normalize(card.PassiveText + "\nPress " + _platform.OpenKeyLabel + " to review."), val2); } private void DrawModal(Release1DecisionPromptCard card) { //IL_0062: 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) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007f: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Expected O, but got Unknown //IL_008a: Expected O, but got Unknown //IL_0090: 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_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) //IL_00d5: Expected O, but got Unknown //IL_0178: 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_01c8: Unknown result type (might be due to invalid IL or missing references) float num = Mathf.Min(620f, Mathf.Max(360f, (float)Screen.width - 32f)); float num2 = 340f; Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width / 2f - num / 2f, (float)Screen.height / 2f - num2 / 2f, num, num2); GUIStyle val2 = new GUIStyle(GUI.skin.box) { fontSize = 19, wordWrap = true, padding = new RectOffset(22, 22, 20, 18) }; val2.normal.textColor = Color.white; GUI.Box(val, Release1PlayerCopy.Normalize(card.Title + "\n\n" + card.Body), val2); GUIStyle val3 = new GUIStyle(GUI.skin.button) { fontSize = 18 }; float num3 = ((Rect)(ref val)).yMax - 58f; if (card.SecondaryAction == null) { if (!_commandIssuedThisFrame && GUI.Button(new Rect(((Rect)(ref val)).x + 22f, num3, ((Rect)(ref val)).width - 44f, 40f), Release1PlayerCopy.Normalize(card.PrimaryLabel + " [Enter]"), val3)) { Execute(card.PrimaryAction); } return; } float num4 = (((Rect)(ref val)).width - 66f) / 2f; if (!_commandIssuedThisFrame && GUI.Button(new Rect(((Rect)(ref val)).x + 22f, num3, num4, 40f), Release1PlayerCopy.Normalize(card.PrimaryLabel + " [Enter]"), val3)) { Execute(card.PrimaryAction); } if (!_commandIssuedThisFrame && GUI.Button(new Rect(((Rect)(ref val)).x + 44f + num4, num3, num4, 40f), Release1PlayerCopy.Normalize(card.SecondaryLabel + " [N]"), val3)) { Execute(card.SecondaryAction); } } } public sealed class Release1DecisionPromptUnityPlatform : IRelease1DecisionPromptPlatform { private readonly KeyCode _openKey; public bool CameraAvailable { get { try { return PlayerSingleton.InstanceExists; } catch { return false; } } } public bool VanillaUiOwnsCursor { get { try { if (PlayerSingleton.InstanceExists && PlayerSingleton.Instance.IsOpen) { return true; } } catch { } try { if (Singleton.InstanceExists && Singleton.Instance.IsPaused) { return true; } } catch { } return false; } } public Release1CursorLockState CursorLockState { get { //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_0006: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Invalid comparison between Unknown and I4 CursorLockMode lockState = Cursor.lockState; if ((int)lockState != 1) { if ((int)lockState == 2) { return Release1CursorLockState.Confined; } return Release1CursorLockState.None; } return Release1CursorLockState.Locked; } } public bool CursorVisible => Cursor.visible; public string OpenKeyLabel => ((object)_openKey/*cast due to .constrained prefix*/).ToString(); public Release1DecisionPromptUnityPlatform(KeyCode openKey = (KeyCode)288) { //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) _openKey = openKey; } public Release1DecisionPromptInput ReadInput(bool modalOpen) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) return MapInput(modalOpen, _openKey, (Func)Input.GetKeyDown); } internal static Release1DecisionPromptInput MapInput(bool modalOpen, KeyCode openKey, Func getKeyDown) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) if (!modalOpen) { if (!getKeyDown(openKey)) { return Release1DecisionPromptInput.None; } return Release1DecisionPromptInput.Open; } if (getKeyDown((KeyCode)27)) { return Release1DecisionPromptInput.Close; } if (getKeyDown((KeyCode)110)) { return Release1DecisionPromptInput.Secondary; } if (getKeyDown((KeyCode)13) || getKeyDown((KeyCode)271)) { return Release1DecisionPromptInput.Primary; } return Release1DecisionPromptInput.None; } public void AddActiveUiElement(string token) { try { if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.AddActiveUIElement(token); } } catch { } } public void RemoveActiveUiElement(string token) { try { if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.RemoveActiveUIElement(token); } } catch { } } public void SetCanLook(bool canLook) { try { if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.SetCanLook(canLook); } } catch { } } public void FreeMouse() { try { if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.FreeMouse(true); } } catch { } } public void LockMouse() { try { if (PlayerSingleton.InstanceExists) { PlayerSingleton.Instance.LockMouse(true); } } catch { } } public void SetCursor(Release1CursorLockState lockState, bool visible) { //IL_000b: 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_000f: 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) Cursor.lockState = (CursorLockMode)(lockState switch { Release1CursorLockState.Locked => 1, Release1CursorLockState.Confined => 2, _ => 0, }); Cursor.visible = visible; } } public enum Release1FieldContactPursuitLevel { Unknown, None, Investigating, NonLethal, Arresting, Lethal } public sealed record Release1FieldContactSnapshot(bool Present, bool Physical, bool Visible, float DistanceToPlayer, bool Moving, float ContactHealth, float ContactMaxHealth, bool Conscious, bool KnockedOut, bool Dead, float PlayerHealth, float PlayerMaxHealth, bool PlayerUnconscious, bool PlayerArrested, bool PlayerRagdolled, bool PlayerTased, Release1FieldContactPursuitLevel PursuitLevel, bool Wanted, bool LethalAuthorized, bool BodySearchPending, int ActiveOfficerCount, int DispatchOfficerCount, float Aggressiveness, float GiveUpRange, float GiveUpTime, string WeaponAssetPath) { public const float UnsetMeasure = -1f; public const int UnsetCount = -1; public static Release1FieldContactSnapshot Unavailable() { return new Release1FieldContactSnapshot(Present: false, Physical: false, Visible: false, -1f, Moving: false, -1f, -1f, Conscious: false, KnockedOut: false, Dead: false, -1f, -1f, PlayerUnconscious: false, PlayerArrested: false, PlayerRagdolled: false, PlayerTased: false, Release1FieldContactPursuitLevel.Unknown, Wanted: false, LethalAuthorized: false, BodySearchPending: false, -1, -1, -1f, -1f, -1f, string.Empty); } public static Release1FieldContactSnapshot AbsentContact(float playerHealth, float playerMaxHealth, bool playerUnconscious, bool playerArrested, bool playerRagdolled, bool playerTased, Release1FieldContactPursuitLevel pursuitLevel, bool wanted, bool lethalAuthorized, bool bodySearchPending, int activeOfficerCount, int dispatchOfficerCount) { return Unavailable()with { PlayerHealth = playerHealth, PlayerMaxHealth = playerMaxHealth, PlayerUnconscious = playerUnconscious, PlayerArrested = playerArrested, PlayerRagdolled = playerRagdolled, PlayerTased = playerTased, PursuitLevel = pursuitLevel, Wanted = wanted, LethalAuthorized = lethalAuthorized, BodySearchPending = bodySearchPending, ActiveOfficerCount = activeOfficerCount, DispatchOfficerCount = dispatchOfficerCount }; } public void Validate() { if (!Enum.IsDefined(PursuitLevel)) { throw new ArgumentException("Pursuit level is not defined.", "PursuitLevel"); } if (WeaponAssetPath == null) { throw new ArgumentNullException("WeaponAssetPath"); } (string, float)[] array = new(string, float)[8] { ("DistanceToPlayer", DistanceToPlayer), ("ContactHealth", ContactHealth), ("ContactMaxHealth", ContactMaxHealth), ("PlayerHealth", PlayerHealth), ("PlayerMaxHealth", PlayerMaxHealth), ("Aggressiveness", Aggressiveness), ("GiveUpRange", GiveUpRange), ("GiveUpTime", GiveUpTime) }; for (int i = 0; i < array.Length; i++) { (string, float) tuple = array[i]; var (paramName, _) = tuple; if (!float.IsFinite(tuple.Item2)) { throw new ArgumentException("Field contact measures must be finite.", paramName); } } if (ActiveOfficerCount < -1) { throw new ArgumentOutOfRangeException("ActiveOfficerCount", "Officer counts cannot be below the unset value."); } if (DispatchOfficerCount < -1) { throw new ArgumentOutOfRangeException("DispatchOfficerCount", "Officer counts cannot be below the unset value."); } if (Present || (!Physical && !Visible && !Moving && !Conscious && !KnockedOut && !Dead && DistanceToPlayer == -1f && ContactHealth == -1f && ContactMaxHealth == -1f && Aggressiveness == -1f && GiveUpRange == -1f && GiveUpTime == -1f && WeaponAssetPath.Length == 0)) { return; } throw new ArgumentException("An absent contact must carry unset contact fields.", "Present"); } [CompilerGenerated] private Release1FieldContactSnapshot(Release1FieldContactSnapshot original) { Present = original.Present; Physical = original.Physical; Visible = original.Visible; DistanceToPlayer = original.DistanceToPlayer; Moving = original.Moving; ContactHealth = original.ContactHealth; ContactMaxHealth = original.ContactMaxHealth; Conscious = original.Conscious; KnockedOut = original.KnockedOut; Dead = original.Dead; PlayerHealth = original.PlayerHealth; PlayerMaxHealth = original.PlayerMaxHealth; PlayerUnconscious = original.PlayerUnconscious; PlayerArrested = original.PlayerArrested; PlayerRagdolled = original.PlayerRagdolled; PlayerTased = original.PlayerTased; PursuitLevel = original.PursuitLevel; Wanted = original.Wanted; LethalAuthorized = original.LethalAuthorized; BodySearchPending = original.BodySearchPending; ActiveOfficerCount = original.ActiveOfficerCount; DispatchOfficerCount = original.DispatchOfficerCount; Aggressiveness = original.Aggressiveness; GiveUpRange = original.GiveUpRange; GiveUpTime = original.GiveUpTime; WeaponAssetPath = original.WeaponAssetPath; } } public interface IRelease1FieldContactRuntime : IDisposable { Release1SmallCourtesyWorldReadStatus TryReadFieldContact(string contactId, out Release1FieldContactSnapshot snapshot); Release1SmallCourtesyWorldMutationStatus TryParkFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldMutationStatus TryUnparkFieldContact(string contactId, float aheadMetres, out string reason); Release1SmallCourtesyWorldMutationStatus TryDespawnFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldMutationStatus TryProvokeFieldContact(string contactId, out string reason); } public enum Release1HoldRoomReadiness { Ready, NotReady, Unavailable } public sealed record Release1HoldRoomClosetSnapshot(string ClosetGuid, int SlotCount, IReadOnlyList Slots) { public void Validate() { Release1SmallCourtesyAssignment.ValidateStableId(ClosetGuid, "ClosetGuid"); if (SlotCount < 0) { throw new ArgumentOutOfRangeException("SlotCount"); } if (Slots == null) { throw new ArgumentNullException("Slots"); } if (Slots.Count != SlotCount) { throw new ArgumentException("Closet slot count did not match its slot list.", "SlotCount"); } foreach (Release1SmallCourtesySlotSnapshot slot in Slots) { (slot ?? throw new ArgumentException("Closet slots cannot be null.", "Slots")).Validate(); } if (Slots.Select((Release1SmallCourtesySlotSnapshot slot) => slot.SlotIndex).Distinct().Count() != Slots.Count) { throw new ArgumentException("Closet slot indices must be unique.", "Slots"); } } } public sealed record Release1HoldRoomSnapshot(Release1HoldRoomReadiness Readiness, IReadOnlyList Closets) { public static Release1HoldRoomSnapshot NotReady() { return new Release1HoldRoomSnapshot(Release1HoldRoomReadiness.NotReady, Array.Empty()); } public static Release1HoldRoomSnapshot Unavailable() { return new Release1HoldRoomSnapshot(Release1HoldRoomReadiness.Unavailable, Array.Empty()); } public void Validate() { if (!Enum.IsDefined(Readiness)) { throw new ArgumentException("Hold room readiness is not defined.", "Readiness"); } if (Closets == null) { throw new ArgumentNullException("Closets"); } if (Readiness != Release1HoldRoomReadiness.Ready && Closets.Count != 0) { throw new ArgumentException("A room that is not Ready cannot carry closets.", "Closets"); } foreach (Release1HoldRoomClosetSnapshot closet in Closets) { (closet ?? throw new ArgumentException("Closets cannot be null.", "Closets")).Validate(); } if (Closets.Select((Release1HoldRoomClosetSnapshot closet) => closet.ClosetGuid).Distinct(StringComparer.Ordinal).Count() != Closets.Count) { throw new ArgumentException("Closet GUIDs must be unique.", "Closets"); } } } public enum Release1HoldObservation { RoomNotReady, Held, Missing, Ambiguous } public static class Release1HoldRoomClassifier { public static Release1HoldObservation Classify(Release1HoldRoomSnapshot? room, Release1ConsignmentFingerprint fingerprint, int expectedClosetCount, out string? holdingClosetGuid) { ArgumentNullException.ThrowIfNull(fingerprint, "fingerprint"); holdingClosetGuid = null; if ((object)room == null || room.Readiness != Release1HoldRoomReadiness.Ready || room.Closets.Count != expectedClosetCount) { return Release1HoldObservation.RoomNotReady; } int num = 0; bool flag = false; string text = null; foreach (Release1HoldRoomClosetSnapshot item in room.Closets.OrderBy((Release1HoldRoomClosetSnapshot candidate) => candidate.ClosetGuid, StringComparer.Ordinal)) { int num2 = item.Slots.Count(fingerprint.Matches); if (num2 > 0) { num += num2; if (text == null) { text = item.ClosetGuid; } } if (item.Slots.Any((Release1SmallCourtesySlotSnapshot slot) => IsStackedAmbiguity(fingerprint, slot))) { flag = true; } } if (flag || num > 1) { return Release1HoldObservation.Ambiguous; } if (num == 0) { return Release1HoldObservation.Missing; } holdingClosetGuid = text; return Release1HoldObservation.Held; } private static bool IsStackedAmbiguity(Release1ConsignmentFingerprint fingerprint, Release1SmallCourtesySlotSnapshot? slot) { if ((object)slot != null && slot.IsPackaged && slot.Quantity != fingerprint.Quantity && string.Equals(slot.ProductId, fingerprint.ProductId, StringComparison.Ordinal)) { return string.Equals(slot.PackagingId, fingerprint.PackagingId, StringComparison.Ordinal); } return false; } } public enum Release1HoldDecision { Hold, RecordStow, ClearMissing, SatisfyHold, RecordMissing, FailHold, None } public static class Release1HoldWindow { public const double MissingGraceGameMinutes = 60.0; public static Release1HoldDecision Decide(Release1HoldObservation observation, Release1RoomWithNoNameProgress progress, double nowGameMinutes, double holdDurationGameMinutes) { ArgumentNullException.ThrowIfNull(progress, "progress"); return Release1ConditionWindow.Decide(Observe(observation), new Release1WindowProgress(progress.Stowed ? progress.StowedAtGameMinutes : ((double?)null), progress.MissingSincePassGameMinutes, progress.HoldSatisfied), nowGameMinutes, new Release1WindowProfile(holdDurationGameMinutes, 60.0, Release1WindowHealPolicy.ElapseBeforeHeal)) switch { Release1WindowDecision.OpenWindow => Release1HoldDecision.RecordStow, Release1WindowDecision.ClearBreach => Release1HoldDecision.ClearMissing, Release1WindowDecision.RecordBreach => Release1HoldDecision.RecordMissing, Release1WindowDecision.Elapsed => Release1HoldDecision.SatisfyHold, Release1WindowDecision.Fail => Release1HoldDecision.FailHold, Release1WindowDecision.None => Release1HoldDecision.None, _ => Release1HoldDecision.Hold, }; } private static Release1WindowObservation Observe(Release1HoldObservation observation) { return observation switch { Release1HoldObservation.Held => Release1WindowObservation.Clean, Release1HoldObservation.Missing => Release1WindowObservation.Breached, _ => Release1WindowObservation.Unreadable, }; } } public sealed class Release1PayphoneBanner : IRelease1PayphoneCue, IDisposable { public const string Text = "A nearby payphone is ringing. Answer it."; private readonly Func _now; private readonly float _durationSeconds; private string? _activeCorrelationId; private float _expiresAt; private bool _disposed; public bool Visible { get { if (!_disposed && _activeCorrelationId != null) { return _now() < _expiresAt; } return false; } } public Release1PayphoneBanner(Func? now = null, float durationSeconds = 8f) { if (!float.IsFinite(durationSeconds) || durationSeconds <= 0f) { throw new ArgumentOutOfRangeException("durationSeconds"); } _now = now ?? ((Func)(() => Time.unscaledTime)); _durationSeconds = durationSeconds; } public bool TryShow(string correlationId) { if (_disposed || string.IsNullOrWhiteSpace(correlationId)) { return false; } Update(); if (_activeCorrelationId != null) { return string.Equals(_activeCorrelationId, correlationId, StringComparison.Ordinal); } _activeCorrelationId = correlationId; _expiresAt = _now() + _durationSeconds; return true; } public void Update() { if (!_disposed && _activeCorrelationId != null && !(_now() < _expiresAt)) { _activeCorrelationId = null; _expiresAt = 0f; } } public void End(string correlationId) { if (!_disposed && string.Equals(_activeCorrelationId, correlationId, StringComparison.Ordinal)) { _activeCorrelationId = null; _expiresAt = 0f; } } public void Reconcile(string correlationId) { } public void OnGUI() { //IL_004f: 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_0063: 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_0072: 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_0085: Expected O, but got Unknown //IL_0086: Expected O, but got Unknown //IL_008c: Unknown result type (might be due to invalid IL or missing references) Update(); if (Visible) { float num = Mathf.Min(620f, Mathf.Max(320f, (float)Screen.width - 32f)); Rect val = new Rect((float)Screen.width / 2f - num / 2f, 42f, num, 74f); GUIStyle val2 = new GUIStyle(GUI.skin.box) { fontSize = 22, wordWrap = true, padding = new RectOffset(18, 18, 12, 12) }; val2.normal.textColor = Color.white; GUI.Box(val, "A nearby payphone is ringing. Answer it.", val2); } } public void Dispose() { if (!_disposed) { _activeCorrelationId = null; _expiresAt = 0f; _disposed = true; } } } public sealed class Release1IntroPromptPresenter { private const string PromptId = "release1-intro"; private const string PassiveText = "A discreet shipping offer is waiting."; public const string PromptText = "A number you do not know is offering a discreet shipping arrangement."; private readonly Release1TransitionPublisherService _publisher; public Release1DecisionPromptCard? DecisionPrompt { get { if (_publisher.PromptVisible) { return new Release1DecisionPromptCard("release1-intro", "A discreet shipping offer is waiting.", "Discreet shipping offer", "A number you do not know is offering a discreet shipping arrangement.", "Accept", delegate { _publisher.TryAccept(); }, "Not now", delegate { _publisher.TryDefer(); }); } return null; } } public Release1IntroPromptPresenter(Release1TransitionPublisherService publisher) { _publisher = publisher ?? throw new ArgumentNullException("publisher"); } } public sealed class Release1KeepTheLightsOffComposition : IDisposable { private bool _disposed; public Release1KeepTheLightsOffMissionService Service { get; } public Release1KeepTheLightsOffPresenter Presenter { get; } public Release1KeepTheLightsOffComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService phone, Action? log = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(world, "world"); ArgumentNullException.ThrowIfNull(phone, "phone"); Service = new Release1KeepTheLightsOffMissionService(story, world, phone, log); Presenter = new Release1KeepTheLightsOffPresenter(Service, story, log); } public void OnLoadComplete() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnLoadComplete(); } } public void OnPreLoad() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnPreLoad(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); } } public void Update() { if (!_disposed) { Service.Update(); } } public void Dispose() { if (!_disposed) { _disposed = true; Presenter.Dispose(); Service.Dispose(); } } } public enum Release1KeepTheLightsOffOfferStatus { Inactive, Available, Ineligible, Pending, Saving, Unavailable, Dismissed, Disposed } public enum Release1KeepTheLightsOffReviewStatus { Ready, Inactive, Ineligible, Pending, Saving, Unavailable, Dismissed, Disposed } public enum Release1KeepTheLightsOffDecisionStatus { Accepted, Deferred, Dismissed, ReviewRequired, QuoteChanged, PersistenceDeferred, Rejected, Inactive, Disposed } public enum Release1KeepTheLightsOffCensusStatus { NoWork, NotReady, Holding, WindowStarted, BreachRecorded, Failed, Completed, Rejected } public sealed record Release1KeepTheLightsOffQuote(Release1KeepTheLightsOffAssignment Assignment, double? DeadlineDurationHours) { public void Validate() { Assignment.Validate(); if (Assignment.Mode == Release1KeepTheLightsOffAssignmentMode.Recovery) { if (DeadlineDurationHours.HasValue) { throw new ArgumentException("Recovery quotes cannot have a deadline.", "DeadlineDurationHours"); } } else if (DeadlineDurationHours.GetValueOrDefault() != 72.0) { throw new ArgumentException("Timed Keep the Lights Off stages require a 72-hour duration.", "DeadlineDurationHours"); } } } public sealed record Release1KeepTheLightsOffReviewResult(Release1KeepTheLightsOffReviewStatus Status, Release1KeepTheLightsOffQuote? Quote, string Message); public sealed record Release1KeepTheLightsOffDecisionResult(Release1KeepTheLightsOffDecisionStatus Status, string Message); public sealed class Release1KeepTheLightsOffMissionService : IDisposable { private const string TermsVersion = "keep-the-lights-off-v1"; private const double TimedStageDurationHours = 72.0; private const double BreachGraceGameMinutes = 60.0; private const string ArthurCallerLabel = "Arthur Selby"; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1PhoneCallService? _phone; private readonly Action _log; private bool _loadActive; private bool _saving; private bool _dismissed; private bool _disposed; private Release1KeepTheLightsOffQuote? _reviewedQuote; private readonly Release1ConvergenceThrottle _convergence = new Release1ConvergenceThrottle(); private readonly HashSet _reportedHolds = new HashSet(StringComparer.Ordinal); private IReadOnlyList _previousStations = Array.Empty(); private bool _baselinePassPending = true; private static readonly string[] ArthurWarningStages = new string[3] { "Arthur Selby. It did not stay clear and the day never happened.", "Nell has arranged one more run. Clear it again, and hold it for one more day. I'm not going to keep protecting you. Fucking do it right.", "Do not make her ask twice." }; public Release1KeepTheLightsOffOfferStatus OfferStatus { get; private set; } public Release1KeepTheLightsOffQuote? ReviewedQuote => _reviewedQuote; public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public Release1KeepTheLightsOffMissionService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService? phone = null, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _phone = phone; _log = log ?? ((Action)delegate { }); } public void OnLoadComplete() { if (!_disposed && !_loadActive) { _loadActive = true; _saving = false; _dismissed = false; _reviewedQuote = null; _convergence.Clear(); _baselinePassPending = true; _reportedHolds.Clear(); RefreshOfferStatus(); ReconcileCensus(); TryQueueArthur(); } } public void OnPreLoad() { if (!_disposed) { _loadActive = false; _saving = false; _dismissed = false; _reviewedQuote = null; _convergence.Clear(); _baselinePassPending = true; _previousStations = Array.Empty(); _reportedHolds.Clear(); OfferStatus = Release1KeepTheLightsOffOfferStatus.Inactive; } } public void OnSaveStart() { if (!_disposed && _loadActive) { _saving = true; _convergence.Clear(); _baselinePassPending = true; OfferStatus = Release1KeepTheLightsOffOfferStatus.Saving; } } public void OnSaveComplete() { if (!_disposed && _loadActive) { _saving = false; _convergence.Clear(); _baselinePassPending = true; RefreshOfferStatus(); ReconcileCensus(); TryQueueArthur(); } } public void Update() { if (_disposed || !_loadActive || _saving || !TryBeginConvergencePass()) { return; } Release1StoryHostContextSnapshot context; Release1KeepTheLightsOffReviewStatus release1KeepTheLightsOffReviewStatus = ReadMatchingContext(out context); if (release1KeepTheLightsOffReviewStatus != Release1KeepTheLightsOffReviewStatus.Ready) { OfferStatus = Map(release1KeepTheLightsOffReviewStatus); return; } Release1StoryState state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Accepted) { Release1MissionRecord release1MissionRecord = Mission(state); Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive) ? true : false); if (flag && release1MissionRecord.DeadlineGameTimeHours.HasValue && TryReadGameHours(out var gameHours) && gameHours >= release1MissionRecord.DeadlineGameTimeHours.Value) { Release1TransitionKind transition = ((release1MissionRecord.State == Release1MissionState.Active) ? Release1TransitionKind.RequiredFailure : Release1TransitionKind.MakeGoodFailed); string receipt = $"keep-the-lights-off-deadline-{DeadlineToken(release1MissionRecord.State)}-v1-a{release1MissionRecord.Attempt}"; _story.TryExecuteDurably(CreateCommand(transition, release1MissionRecord.Attempt, receipt, null, null, null)); RefreshOfferStatus(); } state2 = release1MissionRecord.State; if ((state2 == Release1MissionState.Offered || state2 == Release1MissionState.MakeGoodOffered || state2 == Release1MissionState.RecoveryAvailable) ? true : false) { RefreshOfferStatus(); } Timing.Measure("census/keep-the-lights-off", () => ReconcileCensus()); TryQueueArthur(); } } private bool TryBeginConvergencePass() { long revision = _story.State?.Revision ?? (-1); double totalMinutes; return _convergence.TryBegin(TryReadGameMinutes(out totalMinutes) ? new double?(totalMinutes) : ((double?)null), revision); } public Release1KeepTheLightsOffReviewResult TryReview() { if (_disposed) { return Review(Release1KeepTheLightsOffReviewStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Review(Release1KeepTheLightsOffReviewStatus.Inactive, "No load epoch is active."); } if (_saving) { return Review(Release1KeepTheLightsOffReviewStatus.Saving, "Mission review is deferred during saving."); } if (_dismissed) { return Review(Release1KeepTheLightsOffReviewStatus.Dismissed, "Offer was dismissed for this load."); } Release1KeepTheLightsOffQuote quote; Release1KeepTheLightsOffReviewStatus release1KeepTheLightsOffReviewStatus = TryBuildQuote(out quote); if (release1KeepTheLightsOffReviewStatus != Release1KeepTheLightsOffReviewStatus.Ready) { _reviewedQuote = null; OfferStatus = Map(release1KeepTheLightsOffReviewStatus); return Review(release1KeepTheLightsOffReviewStatus, Message(release1KeepTheLightsOffReviewStatus)); } _reviewedQuote = quote; OfferStatus = Release1KeepTheLightsOffOfferStatus.Available; return new Release1KeepTheLightsOffReviewResult(Release1KeepTheLightsOffReviewStatus.Ready, quote, "Keep the Lights Off terms are ready for review."); } public Release1KeepTheLightsOffDecisionResult TryAccept() { if (_disposed) { return Decision(Release1KeepTheLightsOffDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _dismissed) { return Decision(Release1KeepTheLightsOffDecisionStatus.Inactive, "No active offer is available."); } if (_saving) { return Decision(Release1KeepTheLightsOffDecisionStatus.PersistenceDeferred, "Acceptance is deferred during saving."); } if ((object)_reviewedQuote == null) { return Decision(Release1KeepTheLightsOffDecisionStatus.ReviewRequired, "Review the current terms before accepting."); } if (TryBuildQuote(out Release1KeepTheLightsOffQuote quote) != Release1KeepTheLightsOffReviewStatus.Ready || quote != _reviewedQuote) { _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1KeepTheLightsOffDecisionStatus.QuoteChanged, "The assignment terms changed; review again."); } if (!TryReadGameHours(out var gameHours)) { return Decision(Release1KeepTheLightsOffDecisionStatus.Rejected, "Canonical game time was unavailable."); } Release1KeepTheLightsOffAssignment assignment = quote.Assignment; Release1StoryCommand command = CreateCommand(assignment.Mode switch { Release1KeepTheLightsOffAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1KeepTheLightsOffAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1KeepTheLightsOffAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }, assignment.Attempt, AcceptanceReceipt(assignment.Mode, assignment.Attempt), (assignment.Mode == Release1KeepTheLightsOffAssignmentMode.Primary) ? "keep-the-lights-off-v1" : null, gameHours, (assignment.Mode == Release1KeepTheLightsOffAssignmentMode.Recovery) ? ((double?)null) : new double?(gameHours + 72.0)); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteKeepTheLightsOffAcceptanceDurably(command, assignment); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1KeepTheLightsOffDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1KeepTheLightsOffDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } _reviewedQuote = null; _convergence.Clear(); _baselinePassPending = true; RefreshOfferStatus(); ReconcileCensus(); TryQueueArthur(); return Decision(Release1KeepTheLightsOffDecisionStatus.Accepted, "Keep the Lights Off stage was accepted."); } public Release1KeepTheLightsOffDecisionResult TryDefer() { if (_disposed) { return Decision(Release1KeepTheLightsOffDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _saving || ReadMatchingContext(out var _) != Release1KeepTheLightsOffReviewStatus.Ready) { return Decision(Release1KeepTheLightsOffDecisionStatus.Inactive, "No active offer is available."); } Release1StoryState state = _story.State; Release1MissionRecord release1MissionRecord = (((object)state == null) ? null : Mission(state)); if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted || (object)release1MissionRecord == null || release1MissionRecord.State != Release1MissionState.Offered) { return TryDismiss(); } string receipt = $"keep-the-lights-off-defer-v1-a{release1MissionRecord.Attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(Release1TransitionKind.MissionDeferred, release1MissionRecord.Attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1KeepTheLightsOffDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1KeepTheLightsOffDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } DismissForLoad(); return Decision(Release1KeepTheLightsOffDecisionStatus.Deferred, "Keep the Lights Off was deferred until a later load."); } public Release1KeepTheLightsOffDecisionResult TryDismiss() { if (_disposed) { return Decision(Release1KeepTheLightsOffDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Decision(Release1KeepTheLightsOffDecisionStatus.Inactive, "No load epoch is active."); } DismissForLoad(); return Decision(Release1KeepTheLightsOffDecisionStatus.Dismissed, "Keep the Lights Off was dismissed for this load."); } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; OfferStatus = Release1KeepTheLightsOffOfferStatus.Disposed; } } private Release1KeepTheLightsOffReviewStatus TryBuildQuote(out Release1KeepTheLightsOffQuote? quote) { try { return TryBuildQuoteCore(out quote); } catch { quote = null; return Release1KeepTheLightsOffReviewStatus.Unavailable; } } private Release1KeepTheLightsOffReviewStatus TryBuildQuoteCore(out Release1KeepTheLightsOffQuote? quote) { quote = null; Release1StoryHostContextSnapshot context; Release1KeepTheLightsOffReviewStatus release1KeepTheLightsOffReviewStatus = ReadMatchingContext(out context); if (release1KeepTheLightsOffReviewStatus != Release1KeepTheLightsOffReviewStatus.Ready) { return release1KeepTheLightsOffReviewStatus; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1KeepTheLightsOffReviewStatus.Ineligible; } if (state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")].State != Release1MissionState.Satisfied) { return Release1KeepTheLightsOffReviewStatus.Ineligible; } Release1MissionRecord release1MissionRecord = Mission(state); Release1KeepTheLightsOffAssignmentMode? release1KeepTheLightsOffAssignmentMode = release1MissionRecord.State switch { Release1MissionState.Offered => Release1KeepTheLightsOffAssignmentMode.Primary, Release1MissionState.MakeGoodOffered => Release1KeepTheLightsOffAssignmentMode.MakeGood, Release1MissionState.RecoveryAvailable => Release1KeepTheLightsOffAssignmentMode.Recovery, _ => null, }; if (!release1KeepTheLightsOffAssignmentMode.HasValue) { return Release1KeepTheLightsOffReviewStatus.Ineligible; } int attempt = ((release1KeepTheLightsOffAssignmentMode == Release1KeepTheLightsOffAssignmentMode.Primary) ? release1MissionRecord.Attempt : (release1MissionRecord.Attempt + 1)); Release1TransitionKind transitionKind = release1KeepTheLightsOffAssignmentMode switch { Release1KeepTheLightsOffAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1KeepTheLightsOffAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1KeepTheLightsOffAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string value = Release1LogicalCorrelation.Create(context.PlayerId, "release1.keep-the-lights-off", attempt, transitionKind, AcceptanceReceipt(release1KeepTheLightsOffAssignmentMode.Value, attempt)).Value; Release1ProductionActivitySnapshot activity; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadProductionActivity(out activity); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!Release1KeepTheLightsOffAssignmentSelector.TrySelect(activity, release1KeepTheLightsOffAssignmentMode.Value, attempt, value, out Release1KeepTheLightsOffAssignment assignment, out Release1KeepTheLightsOffSelectionStatus _)) { return Release1KeepTheLightsOffReviewStatus.Ineligible; } quote = new Release1KeepTheLightsOffQuote(assignment, (release1KeepTheLightsOffAssignmentMode == Release1KeepTheLightsOffAssignmentMode.Recovery) ? ((double?)null) : new double?(72.0)); quote.Validate(); return Release1KeepTheLightsOffReviewStatus.Ready; } private void RefreshOfferStatus() { if (_disposed) { OfferStatus = Release1KeepTheLightsOffOfferStatus.Disposed; } else if (!_loadActive) { OfferStatus = Release1KeepTheLightsOffOfferStatus.Inactive; } else if (_saving) { OfferStatus = Release1KeepTheLightsOffOfferStatus.Saving; } else if (_dismissed) { OfferStatus = Release1KeepTheLightsOffOfferStatus.Dismissed; } else { OfferStatus = Map(TryBuildQuote(out Release1KeepTheLightsOffQuote _)); } } private Release1KeepTheLightsOffReviewStatus ReadMatchingContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1KeepTheLightsOffReviewStatus.Inactive; } try { Release1StoryHostContextSnapshot context2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadContext(out context2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!SameContext(snapshot, context2)) { return Release1KeepTheLightsOffReviewStatus.Inactive; } context = snapshot; return Release1KeepTheLightsOffReviewStatus.Ready; } catch { return Release1KeepTheLightsOffReviewStatus.Unavailable; } } private bool TryReadGameHours(out double gameHours) { gameHours = 0.0; if (!TryReadGameMinutes(out var totalMinutes)) { return false; } gameHours = totalMinutes / 60.0; return double.IsFinite(gameHours); } private bool TryReadGameMinutes(out double totalMinutes) { totalMinutes = 0.0; try { if (_world.TryReadCanonicalTotalMinutes(out var totalMinutes2) != Release1SmallCourtesyWorldReadStatus.Ready || !double.IsFinite(totalMinutes2) || totalMinutes2 < 0.0) { return false; } totalMinutes = totalMinutes2; return true; } catch { return false; } } private Release1StoryCommand CreateCommand(Release1TransitionKind transition, int attempt, string receipt, string? termsVersion, double? acceptedHours, double? deadlineHours) { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { throw new InvalidOperationException("Story context was not active."); } return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, "release1.keep-the-lights-off", attempt, transition, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.keep-the-lights-off", attempt, transition, receipt).Value, termsVersion, acceptedHours, deadlineHours); } private void DismissForLoad() { _dismissed = true; _reviewedQuote = null; OfferStatus = Release1KeepTheLightsOffOfferStatus.Dismissed; } private static Release1MissionRecord Mission(Release1StoryState state) { return state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; } private static string AcceptanceReceipt(Release1KeepTheLightsOffAssignmentMode mode, int attempt) { return $"keep-the-lights-off-{mode.ToString().ToLowerInvariant()}-accept-v1-a{attempt}"; } private static string DeadlineToken(Release1MissionState state) { if (state != Release1MissionState.Active) { return "make-good"; } return "primary"; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private static Release1KeepTheLightsOffReviewResult Review(Release1KeepTheLightsOffReviewStatus status, string message) { return new Release1KeepTheLightsOffReviewResult(status, null, message); } private static Release1KeepTheLightsOffDecisionResult Decision(Release1KeepTheLightsOffDecisionStatus status, string message) { return new Release1KeepTheLightsOffDecisionResult(status, message); } private static Release1KeepTheLightsOffReviewStatus Map(Release1SmallCourtesyWorldReadStatus status) { if ((uint)(status - 1) <= 1u) { return Release1KeepTheLightsOffReviewStatus.Pending; } return Release1KeepTheLightsOffReviewStatus.Unavailable; } private static Release1KeepTheLightsOffOfferStatus Map(Release1KeepTheLightsOffReviewStatus status) { return status switch { Release1KeepTheLightsOffReviewStatus.Ready => Release1KeepTheLightsOffOfferStatus.Available, Release1KeepTheLightsOffReviewStatus.Inactive => Release1KeepTheLightsOffOfferStatus.Inactive, Release1KeepTheLightsOffReviewStatus.Ineligible => Release1KeepTheLightsOffOfferStatus.Ineligible, Release1KeepTheLightsOffReviewStatus.Pending => Release1KeepTheLightsOffOfferStatus.Pending, Release1KeepTheLightsOffReviewStatus.Saving => Release1KeepTheLightsOffOfferStatus.Saving, Release1KeepTheLightsOffReviewStatus.Dismissed => Release1KeepTheLightsOffOfferStatus.Dismissed, Release1KeepTheLightsOffReviewStatus.Disposed => Release1KeepTheLightsOffOfferStatus.Disposed, _ => Release1KeepTheLightsOffOfferStatus.Unavailable, }; } private static string Message(Release1KeepTheLightsOffReviewStatus status) { return status switch { Release1KeepTheLightsOffReviewStatus.Pending => "World eligibility is still pending.", Release1KeepTheLightsOffReviewStatus.Ineligible => "Keep the Lights Off is not currently offerable.", Release1KeepTheLightsOffReviewStatus.Inactive => "The canonical story context is inactive.", _ => "Keep the Lights Off world data is unavailable.", }; } public Release1KeepTheLightsOffCensusStatus ReconcileCensus() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1KeepTheLightsOffReviewStatus.Ready) { return Release1KeepTheLightsOffCensusStatus.NoWork; } TryRestoreMigratedAssignment(); if (!TryGetActiveStage(out Release1MissionRecord mission, out Release1KeepTheLightsOffAssignment assignment, out string authorization)) { return Release1KeepTheLightsOffCensusStatus.NoWork; } if (!TryReadGameMinutes(out var totalMinutes)) { return Release1KeepTheLightsOffCensusStatus.NoWork; } bool baselinePassPending = _baselinePassPending; Release1ProductionObservationResult release1ProductionObservationResult = ReadObservation(assignment, baselinePassPending); if (release1ProductionObservationResult.Observation != Release1ProductionObservation.NotReady) { _baselinePassPending = false; } _previousStations = release1ProductionObservationResult.Stations; Release1KeepTheLightsOffProgress release1KeepTheLightsOffProgress = Progress(assignment.Attempt) ?? Release1KeepTheLightsOffProgress.Fresh(assignment.Attempt); switch (Release1ConditionWindow.Decide(Release1ProductionActivityClassifier.ToWindowObservation(release1ProductionObservationResult.Observation), new Release1WindowProgress(release1KeepTheLightsOffProgress.ClearConfirmedAtGameMinutes, release1KeepTheLightsOffProgress.BreachSincePassGameMinutes, Satisfied: false), totalMinutes, new Release1WindowProfile(assignment.WindowDurationGameMinutes, 60.0, Release1WindowHealPolicy.HealBeforeElapse))) { case Release1WindowDecision.OpenWindow: if (!SetProgress(assignment.Attempt, totalMinutes, null, clearBreach: true)) { return Release1KeepTheLightsOffCensusStatus.Rejected; } return Release1KeepTheLightsOffCensusStatus.WindowStarted; case Release1WindowDecision.ClearBreach: if (!SetProgress(assignment.Attempt, null, null, clearBreach: true)) { return Release1KeepTheLightsOffCensusStatus.Rejected; } return Release1KeepTheLightsOffCensusStatus.Holding; case Release1WindowDecision.RecordBreach: { int attempt2 = assignment.Attempt; double? breachSincePassGameMinutes = totalMinutes; if (!SetProgress(attempt2, null, breachSincePassGameMinutes)) { return Release1KeepTheLightsOffCensusStatus.Rejected; } return Release1KeepTheLightsOffCensusStatus.BreachRecorded; } case Release1WindowDecision.Fail: return FailWindow(mission, assignment.Attempt); case Release1WindowDecision.Elapsed: return RunCompletion(mission); case Release1WindowDecision.Hold: if (release1ProductionObservationResult.Observation == Release1ProductionObservation.NotReady) { int attempt = assignment.Attempt; authorization = ((release1ProductionObservationResult.Reason != Release1ProductionHoldReason.OwnedPropertyCountMismatch) ? "production activity was not fully readable this pass; the window is unchanged." : "the owned property count no longer matches the count frozen at acceptance; the window is unchanged."); ReportHoldOnce(attempt, authorization); return Release1KeepTheLightsOffCensusStatus.NotReady; } return Release1KeepTheLightsOffCensusStatus.Holding; default: return Release1KeepTheLightsOffCensusStatus.Holding; } } private Release1ProductionObservationResult ReadObservation(Release1KeepTheLightsOffAssignment assignment, bool isBaselinePass) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = Release1SmallCourtesyWorldReadStatus.Faulted; Release1ProductionActivitySnapshot activity = Release1ProductionActivitySnapshot.Empty; try { release1SmallCourtesyWorldReadStatus = _world.TryReadProductionActivity(out activity); } catch { release1SmallCourtesyWorldReadStatus = Release1SmallCourtesyWorldReadStatus.Faulted; activity = Release1ProductionActivitySnapshot.Empty; } try { return Release1ProductionActivityClassifier.Classify(activity, release1SmallCourtesyWorldReadStatus, assignment.ExpectedOwnedPropertyCount, _previousStations, isBaselinePass); } catch (ArgumentException) { return new Release1ProductionObservationResult(Release1ProductionObservation.NotReady, Release1ProductionHoldReason.WorldNotReady, _previousStations); } } private void TryRestoreMigratedAssignment() { Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return; } Release1MissionRecord release1MissionRecord = Mission(state); Release1KeepTheLightsOffAssignmentMode? release1KeepTheLightsOffAssignmentMode = ExpectedAssignmentMode(release1MissionRecord.State); if (!release1KeepTheLightsOffAssignmentMode.HasValue) { return; } int missionAttempt = release1MissionRecord.Attempt; if (state.KeepTheLightsOffAssignments.Any((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == missionAttempt)) { return; } Release1TransitionKind expected = release1KeepTheLightsOffAssignmentMode.Value switch { Release1KeepTheLightsOffAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1KeepTheLightsOffAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, _ => Release1TransitionKind.RecoveryAccepted, }; Release1LogicalCorrelation correlation; string text = release1MissionRecord.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out correlation) && correlation.Attempt == missionAttempt && correlation.TransitionKind == expected); if (!string.IsNullOrEmpty(text) && _world.TryReadProductionActivity(out Release1ProductionActivitySnapshot activity) == Release1SmallCourtesyWorldReadStatus.Ready && Release1KeepTheLightsOffAssignmentSelector.TrySelect(activity, release1KeepTheLightsOffAssignmentMode.Value, missionAttempt, text, out Release1KeepTheLightsOffAssignment assignment, out Release1KeepTheLightsOffSelectionStatus _, requireAssignedEmployee: false)) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryRestoreKeepTheLightsOffAssignment(assignment); if (!release1StoryRuntimeCommandResult.Accepted && release1StoryRuntimeCommandResult.Status != Release1StoryCommandStatus.NoOp) { ReportHoldOnce(missionAttempt, "restoring the migrated assignment was rejected: " + release1StoryRuntimeCommandResult.Message); } } } private Release1KeepTheLightsOffCensusStatus FailWindow(Release1MissionRecord mission, int attempt) { var (transition, text) = mission.State switch { Release1MissionState.Active => (Release1TransitionKind.RequiredFailure, "primary"), Release1MissionState.MakeGoodActive => (Release1TransitionKind.MakeGoodFailed, "make-good"), Release1MissionState.RecoveryActive => (Release1TransitionKind.RecoveryFailed, "recovery"), _ => (Release1TransitionKind.RequiredFailure, string.Empty), }; if (text.Length == 0) { return Release1KeepTheLightsOffCensusStatus.NoWork; } string receipt = $"keep-the-lights-off-breach-{text}-v1-a{attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(transition, attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Release1KeepTheLightsOffCensusStatus.NoWork; } if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(attempt, "recording the breach failure was rejected: " + release1StoryRuntimeCommandResult.Message); return Release1KeepTheLightsOffCensusStatus.Rejected; } RefreshOfferStatus(); return Release1KeepTheLightsOffCensusStatus.Failed; } private bool SetProgress(int attempt, double? clearConfirmedAtGameMinutes = null, double? breachSincePassGameMinutes = null, bool clearBreach = false) { Release1KeepTheLightsOffProgress release1KeepTheLightsOffProgress = Progress(attempt) ?? Release1KeepTheLightsOffProgress.Fresh(attempt); Release1KeepTheLightsOffProgress release1KeepTheLightsOffProgress2 = release1KeepTheLightsOffProgress with { ClearConfirmedAtGameMinutes = (clearConfirmedAtGameMinutes ?? release1KeepTheLightsOffProgress.ClearConfirmedAtGameMinutes), BreachSincePassGameMinutes = (clearBreach ? ((double?)null) : (breachSincePassGameMinutes ?? release1KeepTheLightsOffProgress.BreachSincePassGameMinutes)) }; if (release1KeepTheLightsOffProgress2 == release1KeepTheLightsOffProgress) { return true; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetKeepTheLightsOffProgress(release1KeepTheLightsOffProgress2); if (!release1StoryRuntimeCommandResult.Accepted && release1StoryRuntimeCommandResult.Status != Release1StoryCommandStatus.NoOp) { ReportHoldOnce(attempt, "recording the census progress was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } private Release1KeepTheLightsOffProgress? Progress(int attempt) { return _story.State?.KeepTheLightsOffProgress.SingleOrDefault((Release1KeepTheLightsOffProgress progress) => progress.Attempt == attempt); } private void ReportHoldOnce(int attempt, string message) { if (_reportedHolds.Add($"a{attempt}:{message}")) { _log($"Keep the Lights Off attempt {attempt} is holding: {message}"); } } private Release1KeepTheLightsOffCensusStatus RunCompletion(Release1MissionRecord mission) { Release1MissionState state = mission.State; if ((state != Release1MissionState.Active && state != Release1MissionState.MakeGoodActive && state != Release1MissionState.RecoveryActive) || 1 == 0) { return Release1KeepTheLightsOffCensusStatus.NoWork; } if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1KeepTheLightsOffCensusStatus.NoWork; } string receiptId = $"keep-the-lights-off-complete-v1-a{mission.Attempt}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.keep-the-lights-off", mission.Attempt, Release1TransitionKind.MissionCompleted, receiptId).Value; Release1StoryRuntimeService story = _story; Guid sessionEpoch = snapshot.SessionEpoch; long loadEpoch = snapshot.LoadEpoch; string playerId = snapshot.PlayerId; int attempt = mission.Attempt; Release1CompletionTiming? completionTiming = ((mission.State != Release1MissionState.Active) ? Release1CompletionTiming.Late : Release1CompletionTiming.OnTime); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = story.TryExecute(new Release1StoryCommand(RewardAuthorizationReceiptId: $"keep-the-lights-off-reward-auth-v1-a{mission.Attempt}", SessionEpoch: sessionEpoch, LoadEpoch: loadEpoch, PlayerId: playerId, MissionKey: "release1.keep-the-lights-off", Attempt: attempt, TransitionKind: Release1TransitionKind.MissionCompleted, ReceiptId: receiptId, CorrelationId: value, TermsVersion: null, AcceptedGameTimeHours: null, DeadlineGameTimeHours: null, CompletionTiming: completionTiming)); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(mission.Attempt, "completing the mission after the window elapsed was rejected: " + release1StoryRuntimeCommandResult.Message); return Release1KeepTheLightsOffCensusStatus.Rejected; } RefreshOfferStatus(); return Release1KeepTheLightsOffCensusStatus.Completed; } private void TryQueueArthur() { if (_phone == null || _disposed || !_loadActive || _saving) { return; } Release1StoryState state = _story.State; if ((object)state == null || !_story.TryGetActiveContext(out var snapshot, out var _)) { return; } Release1MissionRecord release1MissionRecord = Mission(state); if (release1MissionRecord.State != Release1MissionState.MakeGoodOffered || release1MissionRecord.LastOutcome != Release1MissionOutcome.RequiredFailure) { return; } try { Release1PhoneCallRequest request = Release1PhoneCallRequest.Create(snapshot, "release1.keep-the-lights-off", release1MissionRecord.Attempt, Release1PhoneCallCorrelation.Create(snapshot.PlayerId, "release1.keep-the-lights-off", release1MissionRecord.Attempt, Release1PhoneCallRole.Arthur, "ktlo-warning-v1"), Release1PhoneCallRole.Arthur, "Arthur Selby", ArthurWarningStages, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.keep-the-lights-off", release1MissionRecord.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-ktlo-accepted-v1").Value); _phone.TryQueue(request); } catch (Exception ex) { _log("Keep the Lights Off Arthur request was not published: " + ex.GetType().Name); } } internal static Release1KeepTheLightsOffAssignmentMode? ExpectedAssignmentMode(Release1MissionState missionState) { switch (missionState) { case Release1MissionState.Accepted: case Release1MissionState.Active: return Release1KeepTheLightsOffAssignmentMode.Primary; case Release1MissionState.MakeGoodOffered: case Release1MissionState.MakeGoodActive: return Release1KeepTheLightsOffAssignmentMode.MakeGood; case Release1MissionState.RecoveryAvailable: case Release1MissionState.RecoveryActive: return Release1KeepTheLightsOffAssignmentMode.Recovery; default: return null; } } private bool TryGetActiveStage(out Release1MissionRecord mission, out Release1KeepTheLightsOffAssignment assignment, out string authorization) { mission = null; assignment = null; authorization = string.Empty; Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return false; } mission = Mission(state); Release1KeepTheLightsOffAssignmentMode? expectedMode = ExpectedAssignmentMode(mission.State); if (!expectedMode.HasValue) { return false; } int missionAttempt = mission.Attempt; assignment = state.KeepTheLightsOffAssignments.SingleOrDefault((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == missionAttempt && candidate.Mode == expectedMode.Value); if ((object)assignment == null) { return false; } authorization = ((expectedMode == Release1KeepTheLightsOffAssignmentMode.Primary) ? (mission.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) && correlation.TransitionKind == Release1TransitionKind.MissionActivated) ?? string.Empty) : assignment.AuthorizationCorrelationId); return !string.IsNullOrEmpty(authorization); } } public enum Release1KeepTheLightsOffCardStage { Hidden, Offer, Review, PersistenceDeferred, DecisionRejected, AwaitingActivation, Running, Holding, Breach, Ambiguous, Completed } public sealed record Release1KeepTheLightsOffViewModel(bool Visible, Release1KeepTheLightsOffCardStage Stage, string Title, string Body, bool CanReview, bool CanAccept, bool CanDefer); public static class Release1KeepTheLightsOffPresentation { public const string Title = "Keep the Lights Off"; public const string OfferText = "You need to go quiet. Period. This is for your own good, be grateful we are warning you."; public const string QuestionText = "Are you taking it?"; public const string ActionText = "Nobody works. Stop all production. Every property, no noise, no grunts working. Nothing."; public const string HoldText = "Then nothing moves for one day."; public const string OwnershipText = "Don't trash product. Just stop working."; public const string PaymentText = "I may help you after. I'll think about it."; public const string RunningStatusText = "You and your employees all need to stop working."; public const string HoldingStatusText = "Hold quiet. Nothing moves until the day is up."; public const string BreachStatusText = "You messed that up. We pulled some strings to give you another chance. Stop production now."; public const string CompletedText = "One quiet day. Thank god, you dodged a bullet there."; public const string AmbiguousStatusText = "Stopped on conflicting evidence. It will not retry."; public static Release1KeepTheLightsOffViewModel Build(Release1StoryState? state, Release1KeepTheLightsOffOfferStatus offerStatus, Release1KeepTheLightsOffQuote? reviewedQuote, Release1KeepTheLightsOffProgress? progress, string? feedback = null, Release1KeepTheLightsOffCardStage? feedbackStage = null) { if (!string.IsNullOrWhiteSpace(feedback)) { return Visible(feedbackStage ?? Release1KeepTheLightsOffCardStage.PersistenceDeferred, Copy(feedback)); } if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Hidden(); } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; if (mission.State == Release1MissionState.Satisfied) { return new Release1KeepTheLightsOffViewModel(Visible: true, Release1KeepTheLightsOffCardStage.Completed, "Keep the Lights Off", Copy("One quiet day. Thank god, you dodged a bullet there."), CanReview: false, CanAccept: false, CanDefer: false); } Release1KeepTheLightsOffAssignmentMode? expectedMode = Release1KeepTheLightsOffMissionService.ExpectedAssignmentMode(mission.State); Release1KeepTheLightsOffAssignment release1KeepTheLightsOffAssignment = state.KeepTheLightsOffAssignments.SingleOrDefault((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == mission.Attempt && (!expectedMode.HasValue || candidate.Mode == expectedMode.Value)); if ((object)release1KeepTheLightsOffAssignment == null) { if ((object)reviewedQuote != null) { return Terms(reviewedQuote.Assignment, reviewedQuote.DeadlineDurationHours); } if (offerStatus == Release1KeepTheLightsOffOfferStatus.Available) { return new Release1KeepTheLightsOffViewModel(Visible: true, Release1KeepTheLightsOffCardStage.Offer, "Keep the Lights Off", Copy("You need to go quiet. Period. This is for your own good, be grateful we are warning you."), CanReview: true, CanAccept: false, CanDefer: false); } return Hidden(); } if (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.keep-the-lights-off" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked)) { return Active(release1KeepTheLightsOffAssignment, mission, Release1KeepTheLightsOffCardStage.Ambiguous, "Stopped on conflicting evidence. It will not retry."); } if (mission.State == Release1MissionState.Accepted) { return Active(release1KeepTheLightsOffAssignment, mission, Release1KeepTheLightsOffCardStage.AwaitingActivation, "You and your employees all need to stop working."); } if ((progress?.BreachSincePassGameMinutes).HasValue) { return Active(release1KeepTheLightsOffAssignment, mission, Release1KeepTheLightsOffCardStage.Breach, "You messed that up. We pulled some strings to give you another chance. Stop production now."); } if ((progress?.ClearConfirmedAtGameMinutes).HasValue) { return Active(release1KeepTheLightsOffAssignment, mission, Release1KeepTheLightsOffCardStage.Holding, "Hold quiet. Nothing moves until the day is up."); } return Active(release1KeepTheLightsOffAssignment, mission, Release1KeepTheLightsOffCardStage.Running, "You and your employees all need to stop working."); } private static Release1KeepTheLightsOffViewModel Terms(Release1KeepTheLightsOffAssignment assignment, double? deadlineHours) { return new Release1KeepTheLightsOffViewModel(Visible: true, Release1KeepTheLightsOffCardStage.Review, "Keep the Lights Off", Copy(Release1PlayerCopy.JoinLines(Release1PlayerCopy.AttemptHeader(assignment.Mode, assignment.Attempt), "Nobody works. Stop all production. Every property, no noise, no grunts working. Nothing.", "Then nothing moves for one day.", "Deadline: " + Release1PlayerCopy.Deadline(deadlineHours), "I may help you after. I'll think about it.", "Don't trash product. Just stop working.", "Are you taking it?")), CanReview: false, CanAccept: true, CanDefer: true); } private static Release1KeepTheLightsOffViewModel Active(Release1KeepTheLightsOffAssignment assignment, Release1MissionRecord mission, Release1KeepTheLightsOffCardStage stage, string status) { return new Release1KeepTheLightsOffViewModel(Visible: true, stage, "Keep the Lights Off", Copy(string.Join("\n", "Status: " + status, "Nobody works. Stop all production. Every property, no noise, no grunts working. Nothing.", "Deadline: " + Release1PlayerCopy.Deadline(DeadlineDuration(mission)), "I may help you after. I'll think about it.")), CanReview: false, CanAccept: false, CanDefer: false); } private static double? DeadlineDuration(Release1MissionRecord mission) { if (mission.DeadlineGameTimeHours.HasValue && mission.AcceptedGameTimeHours.HasValue) { return mission.DeadlineGameTimeHours.Value - mission.AcceptedGameTimeHours.Value; } return null; } private static Release1KeepTheLightsOffViewModel Visible(Release1KeepTheLightsOffCardStage stage, string body) { return new Release1KeepTheLightsOffViewModel(Visible: true, stage, "Keep the Lights Off", body, CanReview: false, CanAccept: false, CanDefer: false); } private static Release1KeepTheLightsOffViewModel Hidden() { return new Release1KeepTheLightsOffViewModel(Visible: false, Release1KeepTheLightsOffCardStage.Hidden, "Keep the Lights Off", string.Empty, CanReview: false, CanAccept: false, CanDefer: false); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1KeepTheLightsOffPresenter : IDisposable { private readonly Release1KeepTheLightsOffMissionService _service; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private string? _feedback; private Release1KeepTheLightsOffCardStage? _feedbackStage; private bool _disposed; public Release1KeepTheLightsOffViewModel View { get { if (!_disposed) { return Release1KeepTheLightsOffPresentation.Build(_story.State, _service.OfferStatus, _service.ReviewedQuote, CurrentProgress(), _feedback, _feedbackStage); } return Release1KeepTheLightsOffPresentation.Build(null, Release1KeepTheLightsOffOfferStatus.Disposed, null, null); } } public Release1KeepTheLightsOffPresenter(Release1KeepTheLightsOffMissionService service, Release1StoryRuntimeService story, Action? log = null) { _service = service ?? throw new ArgumentNullException("service"); _story = story ?? throw new ArgumentNullException("story"); _log = log; } public Release1KeepTheLightsOffReviewResult TryReview() { Release1KeepTheLightsOffReviewResult release1KeepTheLightsOffReviewResult = _service.TryReview(); _feedback = ((release1KeepTheLightsOffReviewResult.Status == Release1KeepTheLightsOffReviewStatus.Ready) ? null : release1KeepTheLightsOffReviewResult.Message); _feedbackStage = ((_feedback == null) ? ((Release1KeepTheLightsOffCardStage?)null) : new Release1KeepTheLightsOffCardStage?(Release1KeepTheLightsOffCardStage.DecisionRejected)); if (_feedback != null) { _log?.Invoke($"Keep the Lights Off review failed: {release1KeepTheLightsOffReviewResult.Status}, {release1KeepTheLightsOffReviewResult.Message}"); } return release1KeepTheLightsOffReviewResult; } public Release1KeepTheLightsOffDecisionResult TryAccept() { return Handle(_service.TryAccept()); } public Release1KeepTheLightsOffDecisionResult TryDefer() { return Handle(_service.TryDefer()); } public void ClearFeedback() { _feedback = null; _feedbackStage = null; } public void Dispose() { _feedback = null; _feedbackStage = null; _disposed = true; } private Release1KeepTheLightsOffProgress? CurrentProgress() { Release1StoryState state = _story.State; if ((object)state == null) { return null; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; return state.KeepTheLightsOffProgress.SingleOrDefault((Release1KeepTheLightsOffProgress progress) => progress.Attempt == mission.Attempt); } private Release1KeepTheLightsOffDecisionResult Handle(Release1KeepTheLightsOffDecisionResult result) { Release1KeepTheLightsOffDecisionStatus status = result.Status; if ((uint)status <= 2u) { _feedback = null; _feedbackStage = null; } else { _feedback = result.Message; _feedbackStage = ((result.Status == Release1KeepTheLightsOffDecisionStatus.PersistenceDeferred) ? Release1KeepTheLightsOffCardStage.PersistenceDeferred : Release1KeepTheLightsOffCardStage.DecisionRejected); _log?.Invoke($"Keep the Lights Off decision failed: {result.Status}, {result.Message}"); } return result; } } public static class Release1KeepTheLightsOffProductionGateHarness { public static Release1StagingHarnessResult TryDumpProductionActivity(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); try { if (!TryRead(world, list, out Release1ProductionActivitySnapshot activity, out Release1StagingHarnessResult failure)) { return failure; } list.Add($"owned properties: {activity.Properties.Count}"); foreach (Release1ProductionPropertySnapshot item in activity.Properties.OrderBy((Release1ProductionPropertySnapshot candidate) => candidate.PropertyCode, StringComparer.Ordinal)) { list.Add($"property {item.PropertyCode} ({item.PropertyName}), employees {item.Employees.Count}, stations {item.Stations.Count}"); foreach (Release1ProductionEmployeeSnapshot item2 in item.Employees.OrderBy((Release1ProductionEmployeeSnapshot candidate) => candidate.EmployeeId, StringComparer.Ordinal)) { list.Add($"employee {item2.EmployeeId} ({item2.DisplayName}) role {item2.Role} fired {item2.Fired} paidForToday {item2.PaidForToday} waitingOutside {item2.IsWaitingOutside} ticksSinceLastWork {item2.TicksSinceLastWork} isAnyWorkInProgress {item2.IsAnyWorkInProgress} behaviour {item2.ActiveBehaviourName ?? "none"} behaviourType {item2.ActiveBehaviourTypeName ?? "none"} behaviourActive {item2.ActiveBehaviourActive}"); } foreach (Release1ProductionStationSnapshot item3 in item.Stations.OrderBy((Release1ProductionStationSnapshot candidate) => candidate.StationGuid, StringComparer.Ordinal)) { list.Add($"station {item3.StationGuid} kind {item3.Kind} running {item3.Running} operation {item3.OperationIdentity ?? "none"} progress {item3.Progress} cookTime {item3.CookTime?.ToString(CultureInfo.InvariantCulture) ?? "n/a"} remainingCookTime {item3.RemainingCookTime?.ToString(CultureInfo.InvariantCulture) ?? "n/a"}"); } } if (activity.Properties.Count == 0) { list.Add("no owned property was reported."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } list.Add("press this key three times a few seconds apart while a cauldron is cooking (this key alternates dump, cross check, dump, so the second press runs the cross check, not a second dump), then compare the cauldron's progress figure and its raw cookTime/remainingCookTime pair between the first and third dumps. A running drying rack's progress is permanently 0 by design; that is not a stalled read."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex) { list.Add("an exception was thrown while dumping production activity: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } public static Release1StagingHarnessResult TryCrossCheckUnpaidEmployees(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); try { if (!TryRead(world, list, out Release1ProductionActivitySnapshot activity, out Release1StagingHarnessResult failure)) { return failure; } (Release1ProductionPropertySnapshot, Release1ProductionEmployeeSnapshot)[] array = (from pair in activity.Properties.SelectMany((Release1ProductionPropertySnapshot property) => property.Employees.Select((Release1ProductionEmployeeSnapshot employee) => (property: property, employee: employee))) where !pair.employee.PaidForToday select pair).OrderBy<(Release1ProductionPropertySnapshot, Release1ProductionEmployeeSnapshot), string>(((Release1ProductionPropertySnapshot property, Release1ProductionEmployeeSnapshot employee) pair) => pair.employee.EmployeeId, StringComparer.Ordinal).ToArray(); if (array.Length == 0) { list.Add("no unpaid employee was reported at any owned property."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } int num = 0; int num2 = 0; (Release1ProductionPropertySnapshot, Release1ProductionEmployeeSnapshot)[] array2 = array; for (int num3 = 0; num3 < array2.Length; num3++) { (Release1ProductionPropertySnapshot, Release1ProductionEmployeeSnapshot) tuple = array2[num3]; Release1ProductionPropertySnapshot item = tuple.Item1; Release1ProductionEmployeeSnapshot item2 = tuple.Item2; bool flag = Release1ProductionWorkBehaviours.ReadsAsWorking(item2); bool flag2 = flag == item2.IsAnyWorkInProgress; if (!flag2) { num++; } if (item2.IsAnyWorkInProgress) { num2++; } list.Add($"unpaid employee {item2.EmployeeId} ({item2.DisplayName}) at {item.PropertyCode} role {item2.Role} isAnyWorkInProgress {item2.IsAnyWorkInProgress} heuristic {flag} agree {flag2} behaviourType {item2.ActiveBehaviourTypeName ?? "none"} ticksSinceLastWork {item2.TicksSinceLastWork}"); } list.Add($"{array.Length} unpaid employee(s), {num} disagreement(s), {num2} working."); return new Release1StagingHarnessResult((num != 0 || num2 != 0) ? Release1StagingHarnessStatus.Faulted : Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex) { list.Add("an exception was thrown while cross checking unpaid employees: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } private static bool TryRead(IRelease1SmallCourtesyWorld world, List lines, out Release1ProductionActivitySnapshot? activity, out Release1StagingHarnessResult failure) { Release1ProductionActivitySnapshot activity2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadProductionActivity(out activity2); if (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready && (object)activity2 != null) { activity = activity2; failure = null; return true; } activity = null; lines.Add($"production activity could not be read, status {release1SmallCourtesyWorldReadStatus}."); failure = new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), lines); return false; } private static Release1StagingHarnessStatus MapReadStatus(Release1SmallCourtesyWorldReadStatus status) { return status switch { Release1SmallCourtesyWorldReadStatus.NotAuthoritative => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldReadStatus.Pending => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Ready => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } } public sealed class Release1KeepTheLightsOffQuest : Quest { public const string DisplayTitle = "Keep the Lights Off"; private readonly QuestEntry[] _entries; protected override string Title => Release1PlayerCopy.Normalize("Keep the Lights Off"); protected override string Description => Release1PlayerCopy.Normalize("Keep the Lights Off"); public Release1KeepTheLightsOffQuest() { string text = Release1PlayerCopy.Normalize("Keep the Lights Off"); _entries = (QuestEntry[])(object)new QuestEntry[1] { ((Quest)this).AddEntry(text, (Vector3?)null) }; } public void Project(Release1DesiredQuest quest) { ArgumentNullException.ThrowIfNull(quest, "quest"); if (quest.Entries.Count != _entries.Length) { throw new InvalidOperationException($"Release 1 Keep the Lights Off quest desired entry count ({quest.Entries.Count}) does not match the quest's native entry count ({_entries.Length})."); } for (int i = 0; i < _entries.Length; i++) { ApplyEntry(_entries[i], quest.Entries[i]); } } public IReadOnlyList ReadEntryStates() { return _entries.Select((QuestEntry entry) => MapState(entry.State)).ToArray(); } private static void ApplyEntry(QuestEntry entry, Release1DesiredEntry desired) { //IL_0035: 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) entry.Title = Release1PlayerCopy.Normalize(desired.Text); Release1WorldPoint marker = desired.Marker; entry.POIPosition = (Vector3)(((object)marker != null) ? new Vector3(marker.X, marker.Y, marker.Z) : Vector3.zero); switch (Release1NativePresentationSupport.ClassifyEntryAction(desired.State)) { case Release1QuestEntryProjectionAction.Begin: entry.Begin(); break; case Release1QuestEntryProjectionAction.Complete: entry.Complete(); break; default: entry.SetState((QuestState)0); break; } } private static Release1DesiredEntryState MapState(QuestState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 1) { if ((int)state == 2) { return Release1DesiredEntryState.Complete; } return Release1DesiredEntryState.Inactive; } return Release1DesiredEntryState.Active; } } public static class Release1LockdownGatePatch { private static readonly Release1LockdownGateState State = new Release1LockdownGateState(); private static readonly Release1LockdownGateThrottle Throttle = new Release1LockdownGateThrottle(); private static Action _log = delegate { }; private static bool _patched; public static bool LockdownActive => State.LockdownActive; public static Release1StagingHarnessResult TryEngage(Harmony harmony, Action log, Func isAuthoritativeHost) { _log = log ?? ((Action)delegate { }); List list = new List(); try { if (!EnsurePatched(harmony)) { list.Add("the Harmony postfix on the curfew manager's per-minute recompute could not be applied."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } CurfewManager instance = NetworkSingleton.Instance; if (instance == null) { list.Add("the curfew manager instance was not available."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } if (State.LockdownActive) { list.Add(DescribeManager(instance, "the lockdown was already engaged.")); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } bool flag = false; if (!instance.IsEnabled) { if (!isAuthoritativeHost()) { list.Add(DescribeManager(instance, "NEEDS OPTION B: curfew is not enabled on this save and this session is not the authoritative host, so the game's Enable path was not called.")); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.NeedsOptionB, list); } Player local = Player.Local; NetworkConnection val = ((local != null) ? local.Connection : null); if (val == null) { list.Add(DescribeManager(instance, "NEEDS OPTION B: curfew is not enabled on this save and no local player connection was available to call Enable with.")); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.NeedsOptionB, list); } instance.Enable(val); flag = true; } State.Engage(flag); Throttle.Reset(); list.Add(DescribeManager(instance, flag ? "engaged; curfew was enabled on this save by this press." : "engaged; curfew was already enabled on this save.")); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex) { list.Add("an exception was thrown while engaging the lockdown: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } public static Release1StagingHarnessResult TryRelease(Func isAuthoritativeHost) { List list = new List(); try { CurfewManager instance = NetworkSingleton.Instance; if (!State.LockdownActive) { list.Add((instance == null) ? "the lockdown was already released." : DescribeManager(instance, "the lockdown was already released.")); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } bool flag = State.Release(); Throttle.Reset(); if (flag) { if (instance == null || !isAuthoritativeHost()) { list.Add("NEEDS OPTION B: curfew was enabled by this gate but could not be disabled again because this session is not the authoritative host or the manager was unavailable."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.NeedsOptionB, list); } instance.Disable(); } list.Add((instance == null) ? "released." : DescribeManager(instance, flag ? "released; curfew was disabled again because this gate had enabled it." : "released; curfew was left enabled because this gate had not enabled it.")); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex) { list.Add("an exception was thrown while releasing the lockdown: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } public static void Reset() { try { if (State.LockdownActive) { TryRelease(() => true); } } catch { } finally { Throttle.Reset(); _log = delegate { }; } } private static bool EnsurePatched(Harmony harmony) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown if (_patched) { return true; } try { MethodInfo methodInfo = AccessTools.Method(typeof(CurfewManager), "OnUncappedMinPass", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(CurfewManager).FullName, "OnUncappedMinPass"); MethodInfo methodInfo2 = AccessTools.Method(typeof(Release1LockdownGatePatch), "OnUncappedMinPassPostfix", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(Release1LockdownGatePatch).FullName, "OnUncappedMinPassPostfix"); harmony.Patch((MethodBase)methodInfo, (HarmonyMethod)null, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); _patched = true; return true; } catch (Exception ex) { _log("the postfix on CurfewManager.OnUncappedMinPass was not applied: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void OnUncappedMinPassPostfix(CurfewManager __instance) { if (!State.LockdownActive || __instance == null) { return; } try { MethodInfo methodInfo = AccessTools.PropertySetter(typeof(CurfewManager), "IsCurrentlyActive"); MethodInfo methodInfo2 = AccessTools.PropertySetter(typeof(CurfewManager), "IsHardCurfewActive"); methodInfo?.Invoke(__instance, new object[1] { true }); methodInfo2?.Invoke(__instance, new object[1] { true }); TimeManager instance = NetworkSingleton.Instance; int minuteKey = ((instance != null) ? instance.CurrentTime : (-1)); if (Throttle.TryMark(minuteKey)) { _log(DescribeManager(__instance, "re-asserted curfew active on the native per-minute recompute.")); } } catch (Exception ex) { _log("re-assert failed on the native per-minute recompute: " + ex.GetType().Name + ": " + ex.Message); } } private static string DescribeManager(CurfewManager manager, string suffix) { TimeManager instance = NetworkSingleton.Instance; int? num = ((instance != null) ? new int?(instance.CurrentTime) : ((int?)null)); return $"enabled={manager.IsEnabled} active={manager.IsCurrentlyActive} hardActive={manager.IsHardCurfewActive} gameHour={(num.HasValue ? num.Value.ToString() : "unknown")} patchApplied={_patched} {suffix}"; } } public sealed class Release1LockdownGateState { public bool LockdownActive { get; private set; } public bool CurfewEnabledByGate { get; private set; } public void Engage(bool curfewEnabledByThisCall) { if (!LockdownActive) { LockdownActive = true; CurfewEnabledByGate = curfewEnabledByThisCall; } } public bool Release() { if (!LockdownActive) { return false; } bool curfewEnabledByGate = CurfewEnabledByGate; LockdownActive = false; CurfewEnabledByGate = false; return curfewEnabledByGate; } } public sealed class Release1LockdownGateThrottle { private int? _lastLoggedMinuteKey; public bool TryMark(int minuteKey) { if (_lastLoggedMinuteKey.HasValue && _lastLoggedMinuteKey.Value == minuteKey) { return false; } _lastLoggedMinuteKey = minuteKey; return true; } public void Reset() { _lastLoggedMinuteKey = null; } } public interface IRelease1MessagingNpc { IReadOnlyDictionary LoadedResponses { get; } bool TryGetResponse(string label, out Response? response); } public enum Release1NativePresentationStatus { Succeeded, Unavailable, Rejected, Faulted } public sealed record Release1ObservedDecision(string? Id, IReadOnlyList Labels, string? Prompt); public sealed record Release1ObservedQuest(string Key, IReadOnlyList EntryStates); public interface IRelease1NativePresentation { Release1NativePresentationStatus TryEnsureContact(); Release1NativePresentationStatus TryReadSentMessages(out IReadOnlyList texts); Release1NativePresentationStatus TryReadDecision(out Release1ObservedDecision? decision); Release1NativePresentationStatus TrySendMessage(string text); Release1NativePresentationStatus TrySetDecision(Release1DesiredDecision decision, Action onChosen); Release1NativePresentationStatus TryClearDecision(); Release1NativePresentationStatus TryReadQuest(string key, out Release1ObservedQuest? quest); Release1NativePresentationStatus TryApplyQuest(Release1DesiredQuest quest); Release1NativePresentationStatus TryEndQuest(string key); void OnPreLoad(); void OnSaveStart(); } public interface IRelease1PresentationCommandSink { bool TryInvoke(Release1PresentationCommand command); } public enum Release1QuestPersistencePolicy { S1ApiPersisted, DisposablePerLoad } public static class Release1QuestPersistencePolicyParser { public static Release1QuestPersistencePolicy Parse(string? value) { if (!string.Equals(value, "S1ApiPersisted", StringComparison.OrdinalIgnoreCase)) { return Release1QuestPersistencePolicy.DisposablePerLoad; } return Release1QuestPersistencePolicy.S1ApiPersisted; } } public enum Release1QuestEntryProjectionAction { SetInactive, Begin, Complete } public enum Release1ContactResolutionOutcome { Create, Adopt, WrongType } public enum Release1QuestResolutionOutcome { None, Found, WrongType } public static class Release1NativePresentationSupport { public const string NellNpcId = "oc_release1_nell"; public const string ChiefNpcId = "oc_release1_chief_campbell"; public static Release1ContactResolutionOutcome ClassifyContact(IEnumerable<(string? Id, bool IsContact)> candidates, string targetId) { ArgumentNullException.ThrowIfNull(candidates, "candidates"); ArgumentNullException.ThrowIfNull(targetId, "targetId"); foreach (var (a, flag) in candidates) { if (string.Equals(a, targetId, StringComparison.Ordinal)) { return flag ? Release1ContactResolutionOutcome.Adopt : Release1ContactResolutionOutcome.WrongType; } } return Release1ContactResolutionOutcome.Create; } public static Release1QuestResolutionOutcome ClassifyQuestLookup(bool exists, bool isExpectedType) { if (!exists) { return Release1QuestResolutionOutcome.None; } if (!isExpectedType) { return Release1QuestResolutionOutcome.WrongType; } return Release1QuestResolutionOutcome.Found; } public static bool ShouldBindExistingResponses(IReadOnlyList existingLabels, IReadOnlyList desiredLabels) { ArgumentNullException.ThrowIfNull(existingLabels, "existingLabels"); ArgumentNullException.ThrowIfNull(desiredLabels, "desiredLabels"); return existingLabels.SequenceEqual(desiredLabels, StringComparer.Ordinal); } public static IReadOnlyList NormalizeSentTexts(IEnumerable rawTexts) { ArgumentNullException.ThrowIfNull(rawTexts, "rawTexts"); List list = new List(); foreach (string rawText in rawTexts) { if (rawText != null) { list.Add(Release1PlayerCopy.Normalize(rawText)); } } return list; } public static Release1QuestEntryProjectionAction ClassifyEntryAction(Release1DesiredEntryState state) { return state switch { Release1DesiredEntryState.Active => Release1QuestEntryProjectionAction.Begin, Release1DesiredEntryState.Complete => Release1QuestEntryProjectionAction.Complete, _ => Release1QuestEntryProjectionAction.SetInactive, }; } public static bool TryMatchRebindCommand(IReadOnlyDictionary? activeOptions, string? label, out Release1PresentationCommand command) { command = Release1PresentationCommand.IntroAccept; if (activeOptions == null || label == null) { return false; } return activeOptions.TryGetValue(label, out command); } public static bool AllDesiredLabelsAvailable(IReadOnlyCollection desiredLabels, IReadOnlySet availableLabels) { ArgumentNullException.ThrowIfNull(desiredLabels, "desiredLabels"); ArgumentNullException.ThrowIfNull(availableLabels, "availableLabels"); foreach (string desiredLabel in desiredLabels) { if (!availableLabels.Contains(desiredLabel)) { return false; } } return true; } } public sealed class Release1NellNpc : NPC, IRelease1MessagingNpc { private const string DefaultPortraitNpcId = "lily_turner"; private readonly Dictionary _loadedResponses = new Dictionary(StringComparer.Ordinal); public IReadOnlyDictionary LoadedResponses => _loadedResponses; public Release1NellNpc() : base("oc_release1_nell", "Nell", "Grey", (Sprite)null) { TryApplyPortrait(); } private void TryApplyPortrait() { string text = Release1NellPortraitNpcIdPreference.Read(); if (string.IsNullOrEmpty(text)) { text = "lily_turner"; } try { NPC val = NPC.Get(text); if (((val != null) ? val.Icon : null) == null) { MelonLogger.Msg("[Organized Crime] Nell portrait: no icon found on '" + text + "'; keeping the default."); return; } ((NPC)this).Icon = val.Icon; ((NPC)this).RefreshMessagingIcons(); MelonLogger.Msg("[Organized Crime] Nell portrait borrowed from NPC '" + text + "'."); } catch (Exception ex) { MelonLogger.Msg($"[Organized Crime] Nell portrait borrow from '{text}' failed ({ex.GetType().Name}); keeping the default."); } } protected override void OnResponseLoaded(Response response) { if (response != null) { string label = response.Label; if (label != null) { _loadedResponses[Release1PlayerCopy.Normalize(label)] = response; } } } public bool TryGetResponse(string label, out Response? response) { return _loadedResponses.TryGetValue(Release1PlayerCopy.Normalize(label), out response); } } public static class Release1NellPortraitNpcIdPreference { private const string CategoryIdentifier = "OrganizedCrime"; private const string EntryIdentifier = "NellPortraitNpcId"; private const string DefaultValue = ""; public static string Read() { MelonPreferences_Category val = MelonPreferences.GetCategory("OrganizedCrime") ?? MelonPreferences.CreateCategory("OrganizedCrime"); return (val.GetEntry("NellPortraitNpcId") ?? val.CreateEntry("NellPortraitNpcId", "", (string)null, (string)null, false, false, (ValueValidator)null, (string)null)).Value ?? ""; } } public static class Release1NpcRegistryDumpHarness { public static Release1StagingHarnessResult TryDump() { List list = new List(); try { List nPCRegistry = NPCManager.NPCRegistry; if (nPCRegistry == null) { list.Add("the native NPC registry was not available."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } int count = nPCRegistry.Count; list.Add($"registry count {count}."); for (int i = 0; i < count; i++) { NPC val = nPCRegistry[i]; if (val != null) { string iD; string firstName; string lastName; bool value; try { iD = val.ID; firstName = val.FirstName; lastName = val.LastName; value = val.Avatar != null && val.Avatar.CurrentSettings != null; } catch (Exception ex) { list.Add($"entry {i} could not be read: {ex.GetType().Name}: {ex.Message}"); continue; } list.Add($"id {iD} firstName {firstName} lastName {lastName} hasAvatarSettings {value}"); } } return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex2) { list.Add("an exception was thrown while dumping the NPC registry: " + ex2.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } } public enum Release1PhoneCallRole { Nell, Arthur } public enum Release1PhoneCallResultState { Deferred, Pending, Attempting, Delivered, Ambiguous, Completed, NotAuthoritative, WrongEpoch, IdentityMismatch, Inactive, Saving, OrderRejected, InvalidRequest, Quarantined, Disposed } public static class Release1PhoneCallCorrelation { private const string NellPrefix = "release1-phone-nell-"; private const string ArthurPrefix = "release1-phone-arthur-"; public const string NellPresentationPrefix = "presentation-nell-"; public static string Create(string playerId, string missionKey, int attempt, Release1PhoneCallRole role, string receiptId) { if (string.IsNullOrWhiteSpace(receiptId) || receiptId.Any(char.IsControl) || receiptId.Any(char.IsWhiteSpace)) { throw new ArgumentException("Phone-call receipt must be non-empty and whitespace-free.", "receiptId"); } return Release1LogicalCorrelation.Create(playerId, missionKey, attempt, Release1TransitionKind.MissionAccepted, role switch { Release1PhoneCallRole.Nell => "release1-phone-nell-", Release1PhoneCallRole.Arthur => "release1-phone-arthur-", _ => throw new ArgumentOutOfRangeException("role"), } + receiptId).Value; } public static bool MatchesRole(Release1LogicalCorrelation correlation, Release1PhoneCallRole role) { return role switch { Release1PhoneCallRole.Nell => correlation.ReceiptId.StartsWith("release1-phone-nell-", StringComparison.Ordinal), Release1PhoneCallRole.Arthur => correlation.ReceiptId.StartsWith("release1-phone-arthur-", StringComparison.Ordinal), _ => false, }; } public static bool IsNellPrerequisite(Release1LogicalCorrelation correlation) { if (!MatchesRole(correlation, Release1PhoneCallRole.Nell)) { return correlation.ReceiptId.StartsWith("presentation-nell-", StringComparison.Ordinal); } return true; } } public sealed record Release1PhoneCallRequest(Release1StoryHostContextSnapshot Context, string MissionKey, int Attempt, string CorrelationId, Release1PhoneCallRole Role, string CallerLabel, IReadOnlyList StageTexts, string? RequiredPriorCorrelationId) { public static Release1PhoneCallRequest Create(Release1StoryHostContextSnapshot context, string missionKey, int attempt, string correlationId, Release1PhoneCallRole role, string callerLabel, IReadOnlyList stageTexts, string? requiredPriorCorrelationId = null) { if (context.SessionEpoch == Guid.Empty || context.LoadEpoch < 0 || string.IsNullOrWhiteSpace(context.PlayerId) || string.IsNullOrWhiteSpace(context.ActiveSaveFolder)) { throw new ArgumentException("Phone-call host context was invalid.", "context"); } if (!Release1MissionCatalog.IsMissionKey(missionKey) || attempt < 1) { throw new ArgumentException("Phone-call mission or attempt was invalid.", "missionKey"); } if (!Release1LogicalCorrelation.TryParse(correlationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != context.PlayerId || correlation.MissionKey != missionKey || correlation.Attempt != attempt) { throw new ArgumentException("Phone-call correlation was not canonical for the request.", "correlationId"); } if (!Enum.IsDefined(role)) { throw new ArgumentOutOfRangeException("role"); } if (!Release1PhoneCallCorrelation.MatchesRole(correlation, role)) { throw new ArgumentException("Phone-call correlation did not carry the request role tag.", "correlationId"); } if (string.IsNullOrWhiteSpace(callerLabel) || callerLabel.Length > 256 || callerLabel.Any(char.IsControl)) { throw new ArgumentException("Caller label must be bounded and control-character-free.", "callerLabel"); } ArgumentNullException.ThrowIfNull(stageTexts, "stageTexts"); string[] array = stageTexts.ToArray(); if (array.Length == 0 || array.Any((string stage) => string.IsNullOrWhiteSpace(stage) || stage.Length > 2048 || stage.Any(char.IsControl))) { throw new ArgumentException("Phone-call stages must be non-empty, bounded, and control-character-free.", "stageTexts"); } if (role == Release1PhoneCallRole.Arthur && (!Release1LogicalCorrelation.TryParse(requiredPriorCorrelationId, out Release1LogicalCorrelation correlation2) || correlation2.PlayerId != context.PlayerId || correlation2.MissionKey != missionKey || correlation2.Attempt != attempt || !Release1PhoneCallCorrelation.IsNellPrerequisite(correlation2))) { throw new ArgumentException("Arthur calls require a matching canonical Nell prerequisite.", "requiredPriorCorrelationId"); } if (role == Release1PhoneCallRole.Nell && requiredPriorCorrelationId != null) { throw new ArgumentException("Nell calls cannot declare an Arthur prerequisite.", "requiredPriorCorrelationId"); } return new Release1PhoneCallRequest(context, missionKey, attempt, correlationId, role, callerLabel, array, requiredPriorCorrelationId); } public void Validate() { Create(Context, MissionKey, Attempt, CorrelationId, Role, CallerLabel, StageTexts, RequiredPriorCorrelationId); } } public interface IRelease1PhoneCallQueue { bool IsAvailable(Release1PhoneCallRequest request); void Invoke(Release1PhoneCallRequest request); } public interface IRelease1PayphoneCue : IDisposable { bool TryShow(string correlationId); void End(string correlationId); void Reconcile(string correlationId); } public sealed record Release1PhoneCallResult(Release1PhoneCallResultState State, bool CueShown, string Message) { public bool Accepted { get { Release1PhoneCallResultState state = State; if ((uint)(state - 1) <= 4u) { return true; } return false; } } } public sealed class Release1PhoneCallService : IDisposable { private readonly Release1StoryRuntimeService _story; private readonly IRelease1PhoneCallQueue _queue; private readonly IRelease1PayphoneCue _cue; private readonly Action _log; private bool _disposed; public Release1PhoneCallService(Release1StoryRuntimeService story, IRelease1PhoneCallQueue queue, IRelease1PayphoneCue cue, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _queue = queue ?? throw new ArgumentNullException("queue"); _cue = cue ?? throw new ArgumentNullException("cue"); _log = log ?? ((Action)delegate { }); } public Release1PhoneCallResult TryQueue(Release1PhoneCallRequest request) { if (_disposed) { return Result(Release1PhoneCallResultState.Disposed, cueShown: false, "Phone-call service was disposed."); } if ((object)request == null) { return Result(Release1PhoneCallResultState.InvalidRequest, cueShown: false, "Phone-call request was null."); } try { request.Validate(); } catch (ArgumentException ex) { _log("Release 1 phone request rejected: " + ex.GetType().Name); return Result(Release1PhoneCallResultState.InvalidRequest, cueShown: false, "Phone-call request was invalid."); } if (!_story.TryGetActiveContext(out var snapshot, out var rejectReason)) { return Result(Map(rejectReason), cueShown: false, "Story runtime was not ready on the canonical host."); } if (request.Context.SessionEpoch != snapshot.SessionEpoch || request.Context.LoadEpoch != snapshot.LoadEpoch) { return Result(Release1PhoneCallResultState.WrongEpoch, cueShown: false, "Phone-call request epoch was stale."); } if (!string.Equals(request.Context.PlayerId, snapshot.PlayerId, StringComparison.Ordinal)) { return Result(Release1PhoneCallResultState.IdentityMismatch, cueShown: false, "Phone-call request identity did not match the host."); } try { if (!SameSaveFolder(request.Context.ActiveSaveFolder, snapshot.ActiveSaveFolder)) { return Result(Release1PhoneCallResultState.InvalidRequest, cueShown: false, "Phone-call request save path did not match the host."); } } catch (Exception ex2) { _log("Release 1 phone save-path validation failed closed: " + ex2.GetType().Name); return Result(Release1PhoneCallResultState.InvalidRequest, cueShown: false, "Phone-call request save path was invalid."); } Release1PhonePresentationAttempt phonePresentationAttempt = _story.GetPhonePresentationAttempt(request.CorrelationId); bool flag = (object)phonePresentationAttempt != null; if (flag) { Release1PhonePresentationAttemptState state = phonePresentationAttempt.State; bool flag2 = (uint)(state - 2) <= 2u; flag = flag2; } if (flag) { return ExistingResult(phonePresentationAttempt.State); } if ((object)phonePresentationAttempt != null && phonePresentationAttempt.State == Release1PhonePresentationAttemptState.Attempting) { return Result(Release1PhoneCallResultState.Ambiguous, cueShown: false, "Phone presentation was left attempting and will not be retried automatically."); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryAuthorizePhonePresentation(request.MissionKey, request.Attempt, request.CorrelationId, request.Role.ToString(), request.RequiredPriorCorrelationId); if (!release1StoryRuntimeCommandResult.Accepted) { return Result(Map(release1StoryRuntimeCommandResult), cueShown: false, release1StoryRuntimeCommandResult.Message); } bool flag3; try { flag3 = _queue.IsAvailable(request); } catch (Exception ex3) { _log("Release 1 phone preflight failed closed: " + ex3.GetType().Name); flag3 = false; } if (!flag3) { return Result(Release1PhoneCallResultState.Pending, cueShown: false, "Phone-call manager was unavailable before invocation."); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = _story.TryTransitionPhonePresentation(request.CorrelationId, Release1PhonePresentationAttemptState.Attempting); if (!release1StoryRuntimeCommandResult2.Accepted) { return Result(Map(release1StoryRuntimeCommandResult2), cueShown: false, release1StoryRuntimeCommandResult2.Message); } try { _queue.Invoke(request); } catch (Exception ex4) { _log("Release 1 phone invocation became ambiguous: " + ex4.GetType().Name); _story.TryTransitionPhonePresentation(request.CorrelationId, Release1PhonePresentationAttemptState.Ambiguous); return Result(Release1PhoneCallResultState.Ambiguous, cueShown: false, "Phone-call invocation threw; automatic retry is forbidden."); } if (!_story.TryTransitionPhonePresentation(request.CorrelationId, Release1PhonePresentationAttemptState.Delivered).Accepted) { return Result(Release1PhoneCallResultState.Ambiguous, cueShown: false, "Phone-call return could not be durably recorded."); } bool flag4 = false; try { flag4 = _cue.TryShow(request.CorrelationId); } catch (Exception ex5) { _log("Release 1 payphone cue failed closed: " + ex5.GetType().Name); } return Result(Release1PhoneCallResultState.Delivered, flag4, flag4 ? "Phone call returned normally and cue was shown." : "Phone call returned normally; cue was unavailable."); } public bool ObserveCompleted(string correlationId) { if (_disposed || string.IsNullOrWhiteSpace(correlationId)) { return false; } Release1PhonePresentationAttempt phonePresentationAttempt = _story.GetPhonePresentationAttempt(correlationId); bool flag = (object)phonePresentationAttempt == null; if (!flag) { Release1PhonePresentationAttemptState state = phonePresentationAttempt.State; bool flag2 = ((state == Release1PhonePresentationAttemptState.Pending || state == Release1PhonePresentationAttemptState.Ambiguous) ? true : false); flag = flag2; } if (flag) { return false; } if (phonePresentationAttempt.State == Release1PhonePresentationAttemptState.Completed) { return true; } if (phonePresentationAttempt.State != Release1PhonePresentationAttemptState.Delivered) { return false; } if (!_story.TryTransitionPhonePresentation(correlationId, Release1PhonePresentationAttemptState.Completed).Accepted) { return false; } try { _cue.End(correlationId); } catch (Exception ex) { _log("Release 1 payphone cue teardown failed closed: " + ex.GetType().Name); } _log("Release 1 phone presentation completed: " + correlationId); return true; } public void ReconcileAfterLoad() { if (_disposed) { return; } foreach (Release1PhonePresentationAttempt phonePresentationAttempt in _story.GetPhonePresentationAttempts()) { try { if (phonePresentationAttempt.State == Release1PhonePresentationAttemptState.Delivered) { _cue.Reconcile(phonePresentationAttempt.CorrelationId); } else if (phonePresentationAttempt.State == Release1PhonePresentationAttemptState.Completed) { _cue.End(phonePresentationAttempt.CorrelationId); } } catch (Exception ex) { _log("Release 1 payphone cue reconciliation failed closed: " + ex.GetType().Name); } } } public void Dispose() { if (!_disposed) { _disposed = true; _cue.Dispose(); } } private Release1PhoneCallResult ExistingResult(Release1PhonePresentationAttemptState state) { return Result(state switch { Release1PhonePresentationAttemptState.Delivered => Release1PhoneCallResultState.Delivered, Release1PhonePresentationAttemptState.Completed => Release1PhoneCallResultState.Completed, _ => Release1PhoneCallResultState.Ambiguous, }, cueShown: false, $"Phone presentation is already {state} and will not be invoked again."); } private static Release1PhoneCallResultState Map(Release1StoryRuntimeRejectReason reason) { switch (reason) { case Release1StoryRuntimeRejectReason.NotAuthoritative: case Release1StoryRuntimeRejectReason.UnsupportedMultiplayer: return Release1PhoneCallResultState.NotAuthoritative; case Release1StoryRuntimeRejectReason.WrongEpoch: return Release1PhoneCallResultState.WrongEpoch; case Release1StoryRuntimeRejectReason.IdentityMismatch: return Release1PhoneCallResultState.IdentityMismatch; case Release1StoryRuntimeRejectReason.Quarantined: return Release1PhoneCallResultState.Quarantined; case Release1StoryRuntimeRejectReason.Disposed: return Release1PhoneCallResultState.Disposed; case Release1StoryRuntimeRejectReason.Inactive: case Release1StoryRuntimeRejectReason.ContextPending: case Release1StoryRuntimeRejectReason.RepositoryPathMismatch: return Release1PhoneCallResultState.Pending; default: return Release1PhoneCallResultState.InvalidRequest; } } private static Release1PhoneCallResultState Map(Release1StoryRuntimeCommandResult result) { if (result.Status != Release1StoryCommandStatus.DeferredSaving) { return Map(result.RejectReason); } return Release1PhoneCallResultState.Pending; } private static Release1PhoneCallResult Result(Release1PhoneCallResultState state, bool cueShown, string message) { return new Release1PhoneCallResult(state, cueShown, message); } private static bool SameSaveFolder(string left, string right) { return string.Equals(Path.GetFullPath(left.Trim()).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.Trim()).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } } public enum Release1CartelStatus { Unknown, Hostile, Truced, Defeated } public enum Release1CartelStatusReadStatus { Ready, Pending, UnsupportedValue, Faulted } public enum Release1PostBenziesUnlockReadStatus { Unlocked, Locked, Pending, NotAuthoritative, UnsupportedMultiplayer, AmbiguousIdentity, Faulted } public readonly record struct Release1PostBenziesUnlockSnapshot(Release1StoryHostContextSnapshot HostContext, Release1CartelStatus CartelStatus); public interface IRelease1CartelStatusSource { Release1CartelStatusReadStatus TryRead(out Release1CartelStatus status); } public interface IRelease1PostBenziesUnlockReader { Release1PostBenziesUnlockReadStatus TryRead(out Release1PostBenziesUnlockSnapshot snapshot); } public sealed class Release1PostBenziesUnlockReader : IRelease1PostBenziesUnlockReader { private readonly IRelease1StoryHostContext _hostContext; private readonly IRelease1CartelStatusSource _cartelStatusSource; public Release1PostBenziesUnlockReader(IRelease1StoryHostContext hostContext, IRelease1CartelStatusSource cartelStatusSource) { _hostContext = hostContext ?? throw new ArgumentNullException("hostContext"); _cartelStatusSource = cartelStatusSource ?? throw new ArgumentNullException("cartelStatusSource"); } public Release1PostBenziesUnlockReadStatus TryRead(out Release1PostBenziesUnlockSnapshot snapshot) { snapshot = default(Release1PostBenziesUnlockSnapshot); try { Release1StoryHostContextSnapshot snapshot2; Release1StoryHostContextReadStatus release1StoryHostContextReadStatus = _hostContext.TryRead(out snapshot2); if (release1StoryHostContextReadStatus != Release1StoryHostContextReadStatus.Ready) { return MapHostStatus(release1StoryHostContextReadStatus); } Release1CartelStatus status; Release1CartelStatusReadStatus release1CartelStatusReadStatus = _cartelStatusSource.TryRead(out status); if (release1CartelStatusReadStatus != Release1CartelStatusReadStatus.Ready) { return (release1CartelStatusReadStatus == Release1CartelStatusReadStatus.Pending) ? Release1PostBenziesUnlockReadStatus.Pending : Release1PostBenziesUnlockReadStatus.Faulted; } snapshot = new Release1PostBenziesUnlockSnapshot(snapshot2, status); return (status != Release1CartelStatus.Defeated) ? Release1PostBenziesUnlockReadStatus.Locked : Release1PostBenziesUnlockReadStatus.Unlocked; } catch { snapshot = default(Release1PostBenziesUnlockSnapshot); return Release1PostBenziesUnlockReadStatus.Faulted; } } private static Release1PostBenziesUnlockReadStatus MapHostStatus(Release1StoryHostContextReadStatus status) { return status switch { Release1StoryHostContextReadStatus.Pending => Release1PostBenziesUnlockReadStatus.Pending, Release1StoryHostContextReadStatus.NotAuthoritative => Release1PostBenziesUnlockReadStatus.NotAuthoritative, Release1StoryHostContextReadStatus.UnsupportedMultiplayer => Release1PostBenziesUnlockReadStatus.UnsupportedMultiplayer, Release1StoryHostContextReadStatus.AmbiguousIdentity => Release1PostBenziesUnlockReadStatus.AmbiguousIdentity, _ => Release1PostBenziesUnlockReadStatus.Faulted, }; } } public enum Release1PresentationMode { ImguiFallback, Native } public static class Release1PresentationModeParser { public static Release1PresentationMode Parse(string? value) { if (!string.Equals(value, "ImguiFallback", StringComparison.OrdinalIgnoreCase)) { return Release1PresentationMode.Native; } return Release1PresentationMode.ImguiFallback; } } public static class Release1PresentationModePreference { private const string CategoryIdentifier = "OrganizedCrime"; private const string EntryIdentifier = "Release1PresentationMode"; private const string DefaultValue = "Native"; public static Release1PresentationMode Read() { MelonPreferences_Category val = MelonPreferences.GetCategory("OrganizedCrime") ?? MelonPreferences.CreateCategory("OrganizedCrime"); return Release1PresentationModeParser.Parse((val.GetEntry("Release1PresentationMode") ?? val.CreateEntry("Release1PresentationMode", "Native", (string)null, (string)null, false, false, (ValueValidator)null, (string)null)).Value); } } public static class Release1QuestPersistencePolicyPreference { private const string CategoryIdentifier = "OrganizedCrime"; private const string EntryIdentifier = "Release1QuestPersistencePolicy"; private const string DefaultValue = "DisposablePerLoad"; public static Release1QuestPersistencePolicy Read() { MelonPreferences_Category val = MelonPreferences.GetCategory("OrganizedCrime") ?? MelonPreferences.CreateCategory("OrganizedCrime"); return Release1QuestPersistencePolicyParser.Parse((val.GetEntry("Release1QuestPersistencePolicy") ?? val.CreateEntry("Release1QuestPersistencePolicy", "DisposablePerLoad", (string)null, (string)null, false, false, (ValueValidator)null, (string)null)).Value); } } public static class Release1ChiefPortraitNpcIdPreference { private const string CategoryIdentifier = "OrganizedCrime"; private const string EntryIdentifier = "ChiefPortraitNpcId"; private const string DefaultValue = ""; public static string Read() { MelonPreferences_Category val = MelonPreferences.GetCategory("OrganizedCrime") ?? MelonPreferences.CreateCategory("OrganizedCrime"); return (val.GetEntry("ChiefPortraitNpcId") ?? val.CreateEntry("ChiefPortraitNpcId", "", (string)null, (string)null, false, false, (ValueValidator)null, (string)null)).Value ?? ""; } } public enum Release1PresentationCommand { IntroAccept, IntroDefer, SmallCourtesyReview, SmallCourtesyAccept, SmallCourtesyDefer, WrongAddressReview, WrongAddressAccept, WrongAddressDefer, RoomWithNoNameReview, RoomWithNoNameAccept, RoomWithNoNameDefer, ShortNoticeReview, ShortNoticeAccept, ShortNoticeDefer, KeepTheLightsOffReview, KeepTheLightsOffAccept, KeepTheLightsOffDefer, TheEnvelopeReview, TheEnvelopeAccept, TheEnvelopeDefer, ChiefCampbellPay, ChiefCampbellDecline } public enum Release1DesiredEntryState { Inactive, Active, Complete } public sealed record Release1DesiredMessage(string CorrelationId, string Text); public sealed record Release1DecisionOption(string Label, Release1PresentationCommand Command); public sealed record Release1DesiredDecision(string Id, string Prompt, IReadOnlyList Options); public sealed record Release1WorldPoint(float X, float Y, float Z); public sealed record Release1DesiredEntry(string Text, Release1DesiredEntryState State, Release1WorldPoint? Marker); public sealed record Release1DesiredQuest(string Key, string Title, IReadOnlyList Entries); public sealed record Release1PresentationPlan(IReadOnlyList Messages, Release1DesiredDecision? Decision, IReadOnlyList Quests, bool DecisionUndetermined = false) { public static Release1PresentationPlan Empty { get; } = new Release1PresentationPlan(Array.Empty(), null, Array.Empty()); [CompilerGenerated] private Release1PresentationPlan(Release1PresentationPlan original) { Messages = original.Messages; Decision = original.Decision; Quests = original.Quests; DecisionUndetermined = original.DecisionUndetermined; } } public sealed record Release1PresentationInputs(Release1StoryState? Story, bool IntroPromptVisible, bool IntroPromptPending, Release1SmallCourtesyViewModel? SmallCourtesy, Release1SmallCourtesyAssignment? SmallCourtesyAssignment, Release1WrongAddressViewModel? WrongAddress = null, Release1WrongAddressAssignment? WrongAddressAssignment = null, Release1WrongAddressProgress? WrongAddressProgress = null, Release1RoomWithNoNameViewModel? RoomWithNoName = null, Release1RoomWithNoNameAssignment? RoomWithNoNameAssignment = null, Release1RoomWithNoNameProgress? RoomWithNoNameProgress = null, Release1WorldPoint? HoldRoomMarker = null, Release1ShortNoticeViewModel? ShortNotice = null, Release1ShortNoticeAssignment? ShortNoticeAssignment = null, Release1ShortNoticeProgress? ShortNoticeProgress = null, Release1KeepTheLightsOffViewModel? KeepTheLightsOff = null, Release1KeepTheLightsOffAssignment? KeepTheLightsOffAssignment = null, Release1KeepTheLightsOffProgress? KeepTheLightsOffProgress = null, Release1TheEnvelopeViewModel? TheEnvelope = null, Release1TheEnvelopeAssignment? TheEnvelopeAssignment = null, Release1TheEnvelopeProgress? TheEnvelopeProgress = null); public static class Release1PresentationPlanner { private const string IntroDecisionId = "release1-intro"; private const string IntroDeferredDecisionId = "release1-intro-deferred"; private const string SmallCourtesyReviewDecisionId = "release1-sc-review"; private const string SmallCourtesyDecisionId = "release1-sc-decision"; private const string IntroAcceptedReceipt = "presentation-intro-accepted-v1"; private const string IntroDeferredReceipt = "presentation-intro-deferred-v1"; private const string SmallCourtesyAcceptedReceipt = "presentation-sc-accepted-v1"; private const string SmallCourtesyCompleteReceipt = "presentation-sc-complete-v1"; private const string IntroOfferText = "A number you do not know is offering a discreet shipping arrangement."; private const string IntroAcceptedText = "You are in. Expect instructions soon."; private const string IntroDeferredText = "Understood. Reach out when you think you can handle it."; private const string IntroDeferredNudgeText = "Still here. The shipping arrangement is on the table when you have the courage to take it up."; private const string SmallCourtesyAcceptedText = "We know you have the package. Stop fucking around and drop it where instructed."; private const string SmallCourtesyCompleteText = "Received and paid. Easy enough."; private const string WaitForPaymentText = "Wait for payment"; private const string PaymentOnItsWayText = "Payment is on its way"; private const string DeliveryOnHoldText = "Delivery on hold"; private const string WrongAddressReviewDecisionId = "release1-wa-review"; private const string WrongAddressDecisionId = "release1-wa-decision"; private const string WrongAddressAcceptedReceipt = "presentation-nell-wa-accepted-v1"; private const string WrongAddressCustodyReceipt = "presentation-nell-wa-custody-v1"; private const string WrongAddressDeliveredReceipt = "presentation-nell-wa-delivered-v1"; private const string WrongAddressDeliveredText = "Received and paid. Mr. Selby noticed."; private const string WrongAddressHoldText = "Delivery on hold"; private const string WrongAddressQuestTitle = "Wrong Address"; private const string RoomWithNoNameReviewDecisionId = "release1-rn-review"; private const string RoomWithNoNameDecisionId = "release1-rn-decision"; private const string RoomWithNoNameAcceptedReceipt = "presentation-nell-rn-accepted-v1"; private const string RoomWithNoNameCustodyReceipt = "presentation-nell-rn-custody-v1"; private const string RoomWithNoNameStowedReceipt = "presentation-nell-rn-stowed-v1"; private const string RoomWithNoNameReleaseReceipt = "presentation-nell-rn-release-v1"; private const string RoomWithNoNameDeliveredReceipt = "presentation-nell-rn-delivered-v1"; private const string RoomWithNoNameDeliveredText = "Received and paid. We now know we can trust HQ."; private const string RoomWithNoNameCustodyText = "You have it. Take it to the room and leave it there."; private const string RoomWithNoNameStowedText = "Good. It stays in the room until I call. One day. Fuck off until then."; private const string RoomWithNoNameHoldEntryText = "Hold it in the room until Nell calls"; private const string RoomWithNoNameHoldText = "Delivery on hold"; private const string RoomWithNoNameQuestTitle = "A Room With No Name"; private const string ShortNoticeReviewDecisionId = "release1-sn-review"; private const string ShortNoticeDecisionId = "release1-sn-decision"; private const string ShortNoticeAcceptedReceipt = "presentation-nell-sn-accepted-v1"; private const string ShortNoticeSpreadReceipt = "presentation-nell-sn-spread-v1"; private const string ShortNoticeDeliveredReceipt = "presentation-nell-sn-delivered-v1"; private const string ShortNoticeQuestTitle = "Short Notice"; private const string ShortNoticeSpreadText = "Put the whole order in one slot. I am not counting two piles."; private const string ShortNoticeShortfallOneText = "That is not the whole order. One more of the same, in the same drop, in one slot. Jesus, do we need to find someone else that is competent?"; private const string ShortNoticeShortfallManyFormat = "That is not the whole order. {0} more of the same, in the same drop, in one slot. Get it together, you fuck."; private const string ShortNoticeDeliveredText = "Received and paid. That is what short notice is worth."; private const string ShortNoticeEntryOpenFormat = "Leave {0} of {1} at {2} before the window closes"; private const string ShortNoticeEntryShortfallFormat = "Leave {0} of {1} at {2}, {3} still needed"; private const string ShortNoticeEntryHoldText = "Delivery on hold"; private const string KeepTheLightsOffReviewDecisionId = "release1-ktlo-review"; private const string KeepTheLightsOffDecisionId = "release1-ktlo-decision"; private const string KeepTheLightsOffAcceptedReceipt = "presentation-nell-ktlo-accepted-v1"; private const string KeepTheLightsOffClearReceipt = "presentation-nell-ktlo-clear-v1"; private const string KeepTheLightsOffCompleteReceipt = "presentation-nell-ktlo-complete-v1"; private const string KeepTheLightsOffQuestTitle = "Keep the Lights Off"; private const string KeepTheLightsOffAcceptedText = "Nobody works. Stop all production. Every property, no noise, no grunts working. Nothing."; private const string KeepTheLightsOffClearConfirmedText = "All stopped. Keep it this way for 24 hours."; private const string KeepTheLightsOffCompletedText = "One quiet day. Thank god, you dodged a bullet there."; private const string KeepTheLightsOffEntryOpenText = "No production activities from you or employees for 24 hours."; private const string KeepTheLightsOffEntryHoldingText = "Hold quiet. Nothing moves for one day"; private const string KeepTheLightsOffEntryHoldText = "Job on hold"; private const string TheEnvelopeReviewDecisionId = "release1-te-review"; private const string TheEnvelopeDecisionId = "release1-te-decision"; private const string TheEnvelopeAcceptedReceipt = "presentation-nell-te-accepted-v1"; private const string TheEnvelopeSpreadReceipt = "presentation-nell-te-spread-v1"; private const string TheEnvelopeDeliveredReceipt = "presentation-nell-te-delivered-v1"; private const string TheEnvelopeRecognitionReceipt = "presentation-nell-te-recognition-v1"; private const string TheEnvelopeQuestTitle = "The Envelope"; private const string TheEnvelopeSpreadText = "Stop fucking around. I told you, all in one storage."; private const string TheEnvelopeShortfallManyFormat = "Are you trying to scam us? You really want to try and test the Selby's?"; private const string TheEnvelopeShortfallSmallFormat = "We know how to count. You are missing {0} dollars more. Put it in the same storage"; private const string TheEnvelopeDeliveredText = "Received. Well done."; private const string TheEnvelopeRecognitionText = "Welcome to the Cheeky Blinders. You are part of the family now. Don't get cocky though, you will earn the respect for increased operations. For now, the HQ room is yours. Use it as you please, but there are times the family may need to use it, and you don't have a fucking choice. I'll be in touch."; private const string TheEnvelopeEntryOpenFormat = "Leave ${0} in cash in the closet at the Syndicate HQ"; private const string TheEnvelopeEntryShortfallFormat = "Leave ${0} in cash in the closet at the Syndicate HQ, ${1} still needed"; private const string TheEnvelopeEntryHoldText = "Delivery on hold"; private static string ShortNoticeShortfallReceipt(int remaining) { return $"presentation-nell-sn-shortfall-r{remaining}-v1"; } private static string TheEnvelopeAcceptedText(double amountWholeDollars) { return $"The closet is in the HQ. {Release1TheEnvelopePresentation.StackCount(amountWholeDollars)} stacks of cash, all in one closet."; } private static string TheEnvelopeShortfallReceipt(double remaining) { return "presentation-nell-te-shortfall-r" + Release1TheEnvelopePresentation.Dollars(remaining) + "-v1"; } public static Release1PresentationPlan Build(Release1PresentationInputs inputs) { ArgumentNullException.ThrowIfNull(inputs, "inputs"); List list = new List(); Release1DesiredDecision decision = null; List quests = new List(); bool decisionUndetermined = false; Release1StoryState story = inputs.Story; switch (story?.RelationshipState ?? Release1RelationshipState.Unstarted) { case Release1RelationshipState.Unstarted: if (inputs.IntroPromptVisible) { decision = new Release1DesiredDecision("release1-intro", Copy("A number you do not know is offering a discreet shipping arrangement."), IntroAcceptOrDeferOptions()); } else if (inputs.IntroPromptPending) { decisionUndetermined = true; } break; case Release1RelationshipState.Deferred: list.Add(IntroMessage(story.PlayerId, Release1TransitionKind.IntroDeferred, "presentation-intro-deferred-v1", "Understood. Reach out when you think you can handle it.")); if (inputs.IntroPromptVisible) { decision = new Release1DesiredDecision("release1-intro-deferred", Copy("Still here. The shipping arrangement is on the table when you have the courage to take it up."), IntroAcceptOrDeferOptions()); } else if (inputs.IntroPromptPending) { decisionUndetermined = true; } break; case Release1RelationshipState.Accepted: list.Add(IntroMessage(story.PlayerId, Release1TransitionKind.IntroAccepted, "presentation-intro-accepted-v1", "You are in. Expect instructions soon.")); break; } if ((object)inputs.SmallCourtesy != null) { BuildSmallCourtesy(story, inputs.SmallCourtesy, inputs.SmallCourtesyAssignment, list, quests, ref decision); } if ((object)inputs.WrongAddress != null) { BuildWrongAddress(story, inputs.WrongAddress, inputs.WrongAddressAssignment, inputs.WrongAddressProgress, list, quests, ref decision); } if ((object)inputs.RoomWithNoName != null) { BuildRoomWithNoName(story, inputs.RoomWithNoName, inputs.RoomWithNoNameAssignment, inputs.RoomWithNoNameProgress, inputs.HoldRoomMarker, list, quests, ref decision); } if ((object)inputs.ShortNotice != null) { BuildShortNotice(story, inputs.ShortNotice, inputs.ShortNoticeAssignment, inputs.ShortNoticeProgress, list, quests, ref decision); } if ((object)inputs.KeepTheLightsOff != null) { BuildKeepTheLightsOff(story, inputs.KeepTheLightsOff, inputs.KeepTheLightsOffAssignment, inputs.KeepTheLightsOffProgress, list, quests, ref decision); } if ((object)inputs.TheEnvelope != null) { BuildTheEnvelope(story, inputs.TheEnvelope, inputs.TheEnvelopeAssignment, inputs.TheEnvelopeProgress, inputs.HoldRoomMarker, list, quests, ref decision); } return new Release1PresentationPlan(list, decision, quests, decisionUndetermined); } private static void BuildSmallCourtesy(Release1StoryState? story, Release1SmallCourtesyViewModel smallCourtesy, Release1SmallCourtesyAssignment? assignment, List messages, List quests, ref Release1DesiredDecision? decision) { Release1MissionRecord release1MissionRecord = story?.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; if (smallCourtesy.CanReview) { decision = new Release1DesiredDecision("release1-sc-review", Copy(smallCourtesy.Body), new Release1DecisionOption[1] { new Release1DecisionOption(Copy("Review terms"), Release1PresentationCommand.SmallCourtesyReview) }); } else if (smallCourtesy.CanAccept) { decision = new Release1DesiredDecision("release1-sc-decision", Copy(smallCourtesy.Body), new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.SmallCourtesyAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.SmallCourtesyDefer) }); } if ((object)story == null || (object)release1MissionRecord == null || (object)assignment == null) { return; } Release1MissionState state = release1MissionRecord.State; if (((uint)(state - 3) <= 1u || state == Release1MissionState.Satisfied) ? true : false) { quests.Add(new Release1DesiredQuest("release1.small-courtesy", Copy("Small Courtesy"), BuildEntries(release1MissionRecord.State, smallCourtesy.Stage, assignment))); if (release1MissionRecord.State == Release1MissionState.Satisfied) { messages.Add(SmallCourtesyMessage(story.PlayerId, release1MissionRecord.Attempt, Release1TransitionKind.MissionCompleted, "presentation-sc-complete-v1", "Received and paid. Easy enough.")); } else { messages.Add(SmallCourtesyMessage(story.PlayerId, release1MissionRecord.Attempt, Release1TransitionKind.MissionAccepted, "presentation-sc-accepted-v1", "We know you have the package. Stop fucking around and drop it where instructed.")); } } } private static IReadOnlyList BuildEntries(Release1MissionState missionState, Release1SmallCourtesyCardStage stage, Release1SmallCourtesyAssignment assignment) { string text = Copy($"Deliver one {assignment.PackagingName} of {assignment.ProductName} to {assignment.DeadDropName}"); if (missionState != Release1MissionState.Satisfied) { if ((uint)(stage - 8) > 2u) { if (stage != Release1SmallCourtesyCardStage.Ambiguous) { Release1WorldPoint marker = new Release1WorldPoint((float)assignment.DeadDropX, (float)assignment.DeadDropY, (float)assignment.DeadDropZ); return new Release1DesiredEntry[2] { new Release1DesiredEntry(text, Release1DesiredEntryState.Active, marker), new Release1DesiredEntry(Copy("Wait for payment"), Release1DesiredEntryState.Inactive, null) }; } return new Release1DesiredEntry[2] { new Release1DesiredEntry(Copy("Delivery on hold"), Release1DesiredEntryState.Active, null), new Release1DesiredEntry(Copy("Wait for payment"), Release1DesiredEntryState.Inactive, null) }; } return new Release1DesiredEntry[2] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(Copy("Payment is on its way"), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[2] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(Copy("Wait for payment"), Release1DesiredEntryState.Complete, null) }; } private static void BuildWrongAddress(Release1StoryState? story, Release1WrongAddressViewModel wrongAddress, Release1WrongAddressAssignment? assignment, Release1WrongAddressProgress? progress, List messages, List quests, ref Release1DesiredDecision? decision) { Release1MissionRecord mission = story?.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; if (wrongAddress.CanReview) { decision = new Release1DesiredDecision("release1-wa-review", Copy(wrongAddress.Body), new Release1DecisionOption[1] { new Release1DecisionOption(Copy("Review terms"), Release1PresentationCommand.WrongAddressReview) }); } else if (wrongAddress.CanAccept) { decision = new Release1DesiredDecision("release1-wa-decision", Copy(wrongAddress.Body), new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.WrongAddressAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.WrongAddressDefer) }); } if ((object)story == null || (object)mission == null || (object)assignment == null) { return; } bool flag; switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: case Release1MissionState.Satisfied: flag = true; break; default: flag = false; break; } if (flag) { bool blocked = story.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.wrong-address" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked); bool flag2 = progress?.Custody ?? false; quests.Add(new Release1DesiredQuest("release1.wrong-address", Copy("Wrong Address"), BuildWrongAddressEntries(mission.State, assignment, flag2, blocked))); switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: flag = true; break; default: flag = false; break; } if (flag) { messages.Add(WrongAddressMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-wa-accepted-v1", $"The package is at {assignment.SourceDropName}. Bring it to {assignment.HandoffDropName}.")); } if (flag2 || mission.State == Release1MissionState.Satisfied) { messages.Add(WrongAddressMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-wa-custody-v1", "We have eyes everywhere, we know you have the package. Take it to " + assignment.HandoffDropName + " NOW.")); } if (mission.State == Release1MissionState.Satisfied) { messages.Add(WrongAddressMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionCompleted, "presentation-nell-wa-delivered-v1", "Received and paid. Mr. Selby noticed.")); } } } private static IReadOnlyList BuildWrongAddressEntries(Release1MissionState missionState, Release1WrongAddressAssignment assignment, bool custody, bool blocked) { string text = Copy("Fix the fuckup and get the package from " + assignment.SourceDropName); string text2 = Copy("Return it to " + assignment.HandoffDropName); Release1WorldPoint marker = new Release1WorldPoint((float)assignment.SourceDropX, (float)assignment.SourceDropY, (float)assignment.SourceDropZ); Release1WorldPoint marker2 = new Release1WorldPoint((float)assignment.HandoffDropX, (float)assignment.HandoffDropY, (float)assignment.HandoffDropZ); if (missionState != Release1MissionState.Satisfied) { if (!blocked) { if (!custody) { return new Release1DesiredEntry[2] { new Release1DesiredEntry(text, Release1DesiredEntryState.Active, marker), new Release1DesiredEntry(text2, Release1DesiredEntryState.Inactive, null) }; } return new Release1DesiredEntry[2] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text2, Release1DesiredEntryState.Active, marker2) }; } return new Release1DesiredEntry[2] { custody ? new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null) : new Release1DesiredEntry(text, Release1DesiredEntryState.Active, marker), new Release1DesiredEntry(Copy("Delivery on hold"), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[2] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text2, Release1DesiredEntryState.Complete, null) }; } private static Release1DesiredMessage WrongAddressMessage(string playerId, int attempt, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.wrong-address", attempt, kind, receipt).Value, Copy(text)); } private static void BuildRoomWithNoName(Release1StoryState? story, Release1RoomWithNoNameViewModel room, Release1RoomWithNoNameAssignment? assignment, Release1RoomWithNoNameProgress? progress, Release1WorldPoint? holdRoomMarker, List messages, List quests, ref Release1DesiredDecision? decision) { Release1MissionRecord mission = story?.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; if (room.CanReview) { decision = new Release1DesiredDecision("release1-rn-review", Copy(room.Body), new Release1DecisionOption[1] { new Release1DecisionOption(Copy("Review terms"), Release1PresentationCommand.RoomWithNoNameReview) }); } else if (room.CanAccept) { decision = new Release1DesiredDecision("release1-rn-decision", Copy(room.Body), new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.RoomWithNoNameAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.RoomWithNoNameDefer) }); } if ((object)story == null || (object)mission == null || (object)assignment == null) { return; } bool flag; switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: case Release1MissionState.Satisfied: flag = true; break; default: flag = false; break; } if (flag) { bool blocked = story.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.room-with-no-name" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked); bool flag2 = progress?.Custody ?? false; bool flag3 = progress?.Stowed ?? false; bool flag4 = progress?.HoldSatisfied ?? false; quests.Add(new Release1DesiredQuest("release1.room-with-no-name", Copy("A Room With No Name"), BuildRoomWithNoNameEntries(mission.State, assignment, flag2, flag4, blocked, holdRoomMarker))); switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: flag = true; break; default: flag = false; break; } if (flag) { messages.Add(RoomWithNoNameMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-rn-accepted-v1", "It is at " + assignment.SourceDropName + ". Bring it to the room and leave it there.")); } if (flag2 || mission.State == Release1MissionState.Satisfied) { messages.Add(RoomWithNoNameMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-rn-custody-v1", "You have it. Take it to the room and leave it there.")); } if (flag3 || mission.State == Release1MissionState.Satisfied) { messages.Add(RoomWithNoNameMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-rn-stowed-v1", "Good. It stays in the room until I call. One day. Fuck off until then.")); } if (flag4 || mission.State == Release1MissionState.Satisfied) { messages.Add(RoomWithNoNameMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-rn-release-v1", "That is long enough. Take it to " + assignment.HandoffDropName + " now.")); } if (mission.State == Release1MissionState.Satisfied) { messages.Add(RoomWithNoNameMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionCompleted, "presentation-nell-rn-delivered-v1", "Received and paid. We now know we can trust HQ.")); } } } private static IReadOnlyList BuildRoomWithNoNameEntries(Release1MissionState missionState, Release1RoomWithNoNameAssignment assignment, bool custody, bool released, bool blocked, Release1WorldPoint? holdRoomMarker) { string text = Copy("Collect the consignment from " + assignment.SourceDropName); string text2 = Copy("Hold it in the room until Nell calls"); string text3 = Copy("Take it to " + assignment.HandoffDropName); Release1WorldPoint marker = new Release1WorldPoint((float)assignment.SourceDropX, (float)assignment.SourceDropY, (float)assignment.SourceDropZ); Release1WorldPoint marker2 = new Release1WorldPoint((float)assignment.HandoffDropX, (float)assignment.HandoffDropY, (float)assignment.HandoffDropZ); if (missionState != Release1MissionState.Satisfied) { if (!blocked) { if (!released) { if (!custody) { return new Release1DesiredEntry[3] { new Release1DesiredEntry(text, Release1DesiredEntryState.Active, marker), new Release1DesiredEntry(text2, Release1DesiredEntryState.Inactive, null), new Release1DesiredEntry(text3, Release1DesiredEntryState.Inactive, null) }; } return new Release1DesiredEntry[3] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text2, Release1DesiredEntryState.Active, holdRoomMarker), new Release1DesiredEntry(text3, Release1DesiredEntryState.Inactive, null) }; } return new Release1DesiredEntry[3] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text2, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text3, Release1DesiredEntryState.Active, marker2) }; } return new Release1DesiredEntry[3] { custody ? new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null) : new Release1DesiredEntry(text, Release1DesiredEntryState.Active, marker), new Release1DesiredEntry(Copy("Delivery on hold"), Release1DesiredEntryState.Active, null), new Release1DesiredEntry(text3, Release1DesiredEntryState.Inactive, null) }; } return new Release1DesiredEntry[3] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text2, Release1DesiredEntryState.Complete, null), new Release1DesiredEntry(text3, Release1DesiredEntryState.Complete, null) }; } private static Release1DesiredMessage RoomWithNoNameMessage(string playerId, int attempt, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.room-with-no-name", attempt, kind, receipt).Value, Copy(text)); } private static void BuildShortNotice(Release1StoryState? story, Release1ShortNoticeViewModel shortNotice, Release1ShortNoticeAssignment? assignment, Release1ShortNoticeProgress? progress, List messages, List quests, ref Release1DesiredDecision? decision) { Release1MissionRecord mission = story?.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; if (shortNotice.CanReview) { decision = new Release1DesiredDecision("release1-sn-review", Copy(shortNotice.Body), new Release1DecisionOption[1] { new Release1DecisionOption(Copy("Review terms"), Release1PresentationCommand.ShortNoticeReview) }); } else if (shortNotice.CanAccept) { decision = new Release1DesiredDecision("release1-sn-decision", Copy(shortNotice.Body), new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.ShortNoticeAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.ShortNoticeDefer) }); } if ((object)story == null || (object)mission == null || (object)assignment == null) { return; } bool flag; switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: case Release1MissionState.Satisfied: flag = true; break; default: flag = false; break; } if (!flag) { return; } bool blocked = story.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.short-notice" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked); quests.Add(new Release1DesiredQuest("release1.short-notice", Copy("Short Notice"), BuildShortNoticeEntries(mission.State, assignment, progress?.LastShortfallNoticed, blocked))); switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: flag = true; break; default: flag = false; break; } if (flag) { messages.Add(ShortNoticeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-sn-accepted-v1", "The drop is " + assignment.HandoffDropName + ". Leave the whole order in one slot.")); } if ((object)progress != null && progress.SpreadNoticed) { messages.Add(ShortNoticeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-sn-spread-v1", "Put the whole order in one slot. I am not counting two piles.")); } int? num = progress?.LastShortfallNoticed; if (num.HasValue) { int valueOrDefault = num.GetValueOrDefault(); if (mission.State != Release1MissionState.Satisfied) { messages.Add(ShortNoticeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, ShortNoticeShortfallReceipt(valueOrDefault), (valueOrDefault == 1) ? "That is not the whole order. One more of the same, in the same drop, in one slot. Jesus, do we need to find someone else that is competent?" : $"That is not the whole order. {valueOrDefault} more of the same, in the same drop, in one slot. Get it together, you fuck.")); } } if (mission.State == Release1MissionState.Satisfied) { messages.Add(ShortNoticeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionCompleted, "presentation-nell-sn-delivered-v1", "Received and paid. That is what short notice is worth.")); } } private static IReadOnlyList BuildShortNoticeEntries(Release1MissionState missionState, Release1ShortNoticeAssignment assignment, int? remaining, bool blocked) { string text = Release1ShortNoticePresentation.Units(assignment.RequiredQuantity, assignment.PackagingName); string text2 = Copy($"Leave {text} of {assignment.ProductName} at {assignment.HandoffDropName} before the window closes"); Release1WorldPoint marker = new Release1WorldPoint((float)assignment.HandoffDropX, (float)assignment.HandoffDropY, (float)assignment.HandoffDropZ); if (missionState != Release1MissionState.Satisfied) { if (!blocked) { if (remaining.HasValue) { int valueOrDefault = remaining.GetValueOrDefault(); return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy($"Leave {text} of {assignment.ProductName} at {assignment.HandoffDropName}, {valueOrDefault} still needed"), Release1DesiredEntryState.Active, marker) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(text2, Release1DesiredEntryState.Active, marker) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy("Delivery on hold"), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(text2, Release1DesiredEntryState.Complete, null) }; } private static Release1DesiredMessage ShortNoticeMessage(string playerId, int attempt, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.short-notice", attempt, kind, receipt).Value, Copy(text)); } private static void BuildKeepTheLightsOff(Release1StoryState? story, Release1KeepTheLightsOffViewModel keepTheLightsOff, Release1KeepTheLightsOffAssignment? assignment, Release1KeepTheLightsOffProgress? progress, List messages, List quests, ref Release1DesiredDecision? decision) { Release1MissionRecord mission = story?.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; if (keepTheLightsOff.CanReview) { decision = new Release1DesiredDecision("release1-ktlo-review", Copy(keepTheLightsOff.Body), new Release1DecisionOption[1] { new Release1DecisionOption(Copy("Review terms"), Release1PresentationCommand.KeepTheLightsOffReview) }); } else if (keepTheLightsOff.CanAccept) { decision = new Release1DesiredDecision("release1-ktlo-decision", Copy(keepTheLightsOff.Body), new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.KeepTheLightsOffAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.KeepTheLightsOffDefer) }); } if ((object)story == null || (object)mission == null) { return; } if (mission.State == Release1MissionState.Satisfied) { quests.Add(new Release1DesiredQuest("release1.keep-the-lights-off", Copy("Keep the Lights Off"), new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy("No production activities from you or employees for 24 hours."), Release1DesiredEntryState.Complete, null) })); messages.Add(KeepTheLightsOffMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionCompleted, "presentation-nell-ktlo-complete-v1", "One quiet day. Thank god, you dodged a bullet there.")); } else { if ((object)assignment == null) { return; } bool flag; switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: flag = true; break; default: flag = false; break; } if (flag) { bool blocked = story.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.keep-the-lights-off" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked); quests.Add(new Release1DesiredQuest("release1.keep-the-lights-off", Copy("Keep the Lights Off"), BuildKeepTheLightsOffEntries(mission.State, progress, blocked))); switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: flag = true; break; default: flag = false; break; } if (flag) { messages.Add(KeepTheLightsOffMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-ktlo-accepted-v1", "Nobody works. Stop all production. Every property, no noise, no grunts working. Nothing.")); } if ((progress?.ClearConfirmedAtGameMinutes).HasValue) { messages.Add(KeepTheLightsOffMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-ktlo-clear-v1", "All stopped. Keep it this way for 24 hours.")); } } } } private static IReadOnlyList BuildKeepTheLightsOffEntries(Release1MissionState missionState, Release1KeepTheLightsOffProgress? progress, bool blocked) { string text = Copy("No production activities from you or employees for 24 hours."); if (missionState != Release1MissionState.Satisfied) { if (!blocked) { if (!(progress?.BreachSincePassGameMinutes).HasValue) { if (!(progress?.ClearConfirmedAtGameMinutes).HasValue) { return new Release1DesiredEntry[1] { new Release1DesiredEntry(text, Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy("Hold quiet. Nothing moves for one day"), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy("You messed that up. We pulled some strings to give you another chance. Stop production now."), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy("Job on hold"), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null) }; } private static Release1DesiredMessage KeepTheLightsOffMessage(string playerId, int attempt, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.keep-the-lights-off", attempt, kind, receipt).Value, Copy(text)); } private static void BuildTheEnvelope(Release1StoryState? story, Release1TheEnvelopeViewModel theEnvelope, Release1TheEnvelopeAssignment? assignment, Release1TheEnvelopeProgress? progress, Release1WorldPoint? holdRoomMarker, List messages, List quests, ref Release1DesiredDecision? decision) { Release1MissionRecord mission = story?.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; if (theEnvelope.CanReview) { decision = new Release1DesiredDecision("release1-te-review", Copy(theEnvelope.Body), new Release1DecisionOption[1] { new Release1DecisionOption(Copy("Review terms"), Release1PresentationCommand.TheEnvelopeReview) }); } else if (theEnvelope.CanAccept) { decision = new Release1DesiredDecision("release1-te-decision", Copy(theEnvelope.Body), new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.TheEnvelopeAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.TheEnvelopeDefer) }); } if ((object)story == null || (object)mission == null || (object)assignment == null) { return; } bool flag; switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: case Release1MissionState.Satisfied: flag = true; break; default: flag = false; break; } if (!flag) { return; } bool blocked = story.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.the-envelope" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked); quests.Add(new Release1DesiredQuest("release1.the-envelope", Copy("The Envelope"), BuildTheEnvelopeEntries(mission.State, assignment, progress?.LastShortfallNoticed, blocked, holdRoomMarker))); switch (mission.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: case Release1MissionState.MakeGoodActive: case Release1MissionState.RecoveryActive: flag = true; break; default: flag = false; break; } if (flag) { messages.Add(TheEnvelopeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-te-accepted-v1", TheEnvelopeAcceptedText(assignment.AmountWholeDollars))); } if ((object)progress != null && progress.SpreadNoticed) { messages.Add(TheEnvelopeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, "presentation-nell-te-spread-v1", "Stop fucking around. I told you, all in one storage.")); } double? num = progress?.LastShortfallNoticed; if (num.HasValue) { double valueOrDefault = num.GetValueOrDefault(); if (mission.State != Release1MissionState.Satisfied) { messages.Add(TheEnvelopeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionActivated, TheEnvelopeShortfallReceipt(valueOrDefault), string.Format((valueOrDefault > 100.0) ? "Are you trying to scam us? You really want to try and test the Selby's?" : "We know how to count. You are missing {0} dollars more. Put it in the same storage", Release1TheEnvelopePresentation.Dollars(valueOrDefault)))); } } if (mission.State == Release1MissionState.Satisfied) { messages.Add(TheEnvelopeMessage(story.PlayerId, mission.Attempt, Release1TransitionKind.MissionCompleted, "presentation-nell-te-delivered-v1", "Received. Well done.")); messages.Add(RecognitionMessage(story.PlayerId, "presentation-nell-te-recognition-v1", "Welcome to the Cheeky Blinders. You are part of the family now. Don't get cocky though, you will earn the respect for increased operations. For now, the HQ room is yours. Use it as you please, but there are times the family may need to use it, and you don't have a fucking choice. I'll be in touch.")); } } private static IReadOnlyList BuildTheEnvelopeEntries(Release1MissionState missionState, Release1TheEnvelopeAssignment assignment, double? remaining, bool blocked, Release1WorldPoint? holdRoomMarker) { string arg = Release1TheEnvelopePresentation.Dollars(assignment.AmountWholeDollars); string text = Copy($"Leave ${arg} in cash in the closet at the Syndicate HQ"); if (missionState != Release1MissionState.Satisfied) { if (!blocked) { if (remaining.HasValue) { double valueOrDefault = remaining.GetValueOrDefault(); return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy($"Leave ${arg} in cash in the closet at the Syndicate HQ, ${Release1TheEnvelopePresentation.Dollars(valueOrDefault)} still needed"), Release1DesiredEntryState.Active, holdRoomMarker) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(text, Release1DesiredEntryState.Active, holdRoomMarker) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(Copy("Delivery on hold"), Release1DesiredEntryState.Active, null) }; } return new Release1DesiredEntry[1] { new Release1DesiredEntry(text, Release1DesiredEntryState.Complete, null) }; } private static Release1DesiredMessage TheEnvelopeMessage(string playerId, int attempt, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.the-envelope", attempt, kind, receipt).Value, Copy(text)); } private static Release1DesiredMessage RecognitionMessage(string playerId, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.intro", 0, Release1TransitionKind.Release1Recognized, receipt).Value, Copy(text)); } private static Release1DesiredMessage IntroMessage(string playerId, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.intro", 0, kind, receipt).Value, Copy(text)); } private static Release1DesiredMessage SmallCourtesyMessage(string playerId, int attempt, Release1TransitionKind kind, string receipt, string text) { return new Release1DesiredMessage(Release1LogicalCorrelation.Create(playerId, "release1.small-courtesy", attempt, kind, receipt).Value, Copy(text)); } private static IReadOnlyList IntroAcceptOrDeferOptions() { return new Release1DecisionOption[2] { new Release1DecisionOption(Copy("Accept"), Release1PresentationCommand.IntroAccept), new Release1DecisionOption(Copy("Not now"), Release1PresentationCommand.IntroDefer) }; } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1PresentationProjector : IDisposable { private readonly Release1StoryRuntimeService _story; private readonly Func _plan; private readonly IRelease1NativePresentation _native; private readonly IRelease1PresentationCommandSink _commands; private readonly Action _log; private readonly bool _reconcileQuests; private bool _saveSuspended; private bool _disposed; public Release1PresentationProjector(Release1StoryRuntimeService story, Func plan, IRelease1NativePresentation native, IRelease1PresentationCommandSink commands, Action? log = null, bool reconcileQuests = true) { _story = story ?? throw new ArgumentNullException("story"); _plan = plan ?? throw new ArgumentNullException("plan"); _native = native ?? throw new ArgumentNullException("native"); _commands = commands ?? throw new ArgumentNullException("commands"); _log = log ?? ((Action)delegate { }); _reconcileQuests = reconcileQuests; } public void OnLoadComplete() { if (!_disposed) { Reconcile(); } } public void OnPreLoad() { _saveSuspended = false; try { _native.OnPreLoad(); } catch (Exception ex) { _log("Release 1 native presentation OnPreLoad faulted: " + ex.GetType().Name); } } public void OnSaveStart() { if (!_disposed) { try { _native.OnSaveStart(); } catch (Exception ex) { _log("Release 1 native presentation OnSaveStart faulted: " + ex.GetType().Name); } _saveSuspended = true; } } public void OnSaveComplete() { if (!_disposed) { _saveSuspended = false; Reconcile(); } } public void Reconcile() { if (_disposed || _saveSuspended || !_story.TryGetActiveContext(out var _, out var _) || _story.Phase != Release1StoryRuntimePhase.Active) { return; } bool loggedThisPass = false; if (TryStatus(() => _native.TryEnsureContact(), LogOnce, "Release 1 native presentation contact check", out var _)) { Release1PresentationPlan release1PresentationPlan; try { release1PresentationPlan = _plan(); } catch (Exception ex) { LogOnce("Release 1 presentation planning faulted: " + ex.GetType().Name); return; } if (ReconcileMessages(release1PresentationPlan.Messages, LogOnce) && TryRead(() => (Status: _native.TryReadDecision(out Release1ObservedDecision decision), Value: decision), LogOnce, "Release 1 native presentation read decision", "reading the decision", out var value) && (release1PresentationPlan.DecisionUndetermined || ReconcileDecision(release1PresentationPlan.Decision, value, LogOnce)) && _reconcileQuests) { ReconcileQuests(release1PresentationPlan.Quests, LogOnce); } } void LogOnce(string message) { if (!loggedThisPass) { loggedThisPass = true; _log(message); } } } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; } } private bool ReconcileMessages(IReadOnlyList messages, Action logOnce) { foreach (Release1DesiredMessage message in messages) { Release1StoryState state = _story.State; if ((object)state == null || !state.PresentationReceipts.Any((Release1PresentationReceipt receipt) => receipt.CorrelationId == message.CorrelationId)) { if (!TryStatus(() => _native.TrySendMessage(message.Text), logOnce, "Release 1 native presentation send message", out var _)) { return false; } if ((object)state != null && Release1LogicalCorrelation.TryParse(message.CorrelationId, out Release1LogicalCorrelation _)) { _story.TryRecordPresentationReceipt(message.CorrelationId); } } } return true; } private bool ReconcileDecision(Release1DesiredDecision? desired, Release1ObservedDecision? observed, Action logOnce) { if ((object)desired == null) { if ((object)observed == null) { return true; } return ApplyDecisionStatus(() => _native.TryClearDecision(), logOnce, "Release 1 native presentation clear decision"); } if ((object)observed != null && DecisionsMatch(desired, observed)) { if (observed.Id != null) { return true; } return ApplyDecisionStatus(() => _native.TrySetDecision(desired, OnDecisionChosen), logOnce, "Release 1 native presentation set decision"); } if ((object)observed != null && !ApplyDecisionStatus(() => _native.TryClearDecision(), logOnce, "Release 1 native presentation clear decision")) { return false; } return ApplyDecisionStatus(() => _native.TrySetDecision(desired, OnDecisionChosen), logOnce, "Release 1 native presentation set decision"); } private static bool ApplyDecisionStatus(Func call, Action logOnce, string description) { Release1NativePresentationStatus release1NativePresentationStatus; try { release1NativePresentationStatus = call(); } catch (Exception ex) { logOnce(description + " faulted: " + ex.GetType().Name); return false; } switch (release1NativePresentationStatus) { case Release1NativePresentationStatus.Faulted: logOnce(description + " reported a fault."); return false; default: logOnce($"{description} did not complete this pass ({release1NativePresentationStatus}); continuing."); break; case Release1NativePresentationStatus.Succeeded: break; } return true; } private bool ReconcileQuests(IReadOnlyList quests, Action logOnce) { foreach (Release1DesiredQuest quest in quests) { if (!ReconcileQuest(quest, logOnce)) { return false; } } HashSet hashSet = new HashSet(quests.Select((Release1DesiredQuest quest) => quest.Key), StringComparer.Ordinal); foreach (Release1MissionDefinition item in Release1MissionCatalog.All) { if (!hashSet.Contains(item.MissionKey) && !ReconcileUndesiredQuest(item.MissionKey, logOnce)) { return false; } } return true; } private bool ReconcileQuest(Release1DesiredQuest quest, Action logOnce) { if (!TryRead(() => (Status: _native.TryReadQuest(quest.Key, out Release1ObservedQuest quest2), Value: quest2), logOnce, "Release 1 native presentation read quest", "reading a quest", out var value)) { return false; } Release1DesiredEntryState[] array = quest.Entries.Select((Release1DesiredEntry entry) => entry.State).ToArray(); if ((object)value != null && value.EntryStates.SequenceEqual(array)) { return true; } if ((object)value == null && array.All((Release1DesiredEntryState state) => state == Release1DesiredEntryState.Complete)) { return true; } Release1NativePresentationStatus status; return TryStatus(() => _native.TryApplyQuest(quest), logOnce, "Release 1 native presentation apply quest", out status); } private bool ReconcileUndesiredQuest(string key, Action logOnce) { if (!TryRead(() => (Status: _native.TryReadQuest(key, out Release1ObservedQuest quest), Value: quest), logOnce, "Release 1 native presentation read quest", "reading a quest", out var value)) { return false; } if ((object)value == null) { return true; } Release1NativePresentationStatus status; return TryStatus(() => _native.TryEndQuest(key), logOnce, "Release 1 native presentation end quest", out status); } private void OnDecisionChosen(Release1PresentationCommand command) { _commands.TryInvoke(command); Reconcile(); } private static bool DecisionsMatch(Release1DesiredDecision desired, Release1ObservedDecision observed) { bool flag = desired.Options.Select((Release1DecisionOption option) => option.Label).SequenceEqual(observed.Labels, StringComparer.Ordinal); if (observed.Id != null) { if (flag) { return string.Equals(desired.Id, observed.Id, StringComparison.Ordinal); } return false; } if (flag) { return string.Equals(CollapseWhitespace(desired.Prompt), CollapseWhitespace(observed.Prompt), StringComparison.Ordinal); } return false; } private static string CollapseWhitespace(string? text) { if (text == null) { return string.Empty; } return Regex.Replace(text.Trim(), "\\s+", " "); } private static bool TryStatus(Func call, Action logOnce, string description, out Release1NativePresentationStatus status) { try { status = call(); } catch (Exception ex) { logOnce(description + " faulted: " + ex.GetType().Name); status = Release1NativePresentationStatus.Faulted; return false; } if (status == Release1NativePresentationStatus.Faulted) { logOnce(description + " reported a fault."); return false; } return status == Release1NativePresentationStatus.Succeeded; } private static bool TryRead(Func<(Release1NativePresentationStatus Status, T Value)> call, Action logOnce, string exceptionDescription, string faultDescription, out T value) { value = default(T); Release1NativePresentationStatus release1NativePresentationStatus; try { (release1NativePresentationStatus, value) = call(); } catch (Exception ex) { logOnce(exceptionDescription + " faulted: " + ex.GetType().Name); return false; } if (release1NativePresentationStatus == Release1NativePresentationStatus.Faulted) { logOnce("Release 1 native presentation reported a fault " + faultDescription + "."); return false; } return release1NativePresentationStatus == Release1NativePresentationStatus.Succeeded; } } public sealed class Release1PresentationCommandRouter : IRelease1PresentationCommandSink { private readonly Release1TransitionPublisherService _publisher; private readonly Release1SmallCourtesyPresenter _presenter; private readonly Release1WrongAddressPresenter? _wrongAddress; private readonly Release1RoomWithNoNamePresenter? _roomWithNoName; private readonly Release1ShortNoticePresenter? _shortNotice; private readonly Release1KeepTheLightsOffPresenter? _keepTheLightsOff; private readonly Release1TheEnvelopePresenter? _theEnvelope; public Release1PresentationCommandRouter(Release1TransitionPublisherService publisher, Release1SmallCourtesyPresenter presenter, Release1WrongAddressPresenter? wrongAddress = null, Release1RoomWithNoNamePresenter? roomWithNoName = null, Release1ShortNoticePresenter? shortNotice = null, Release1KeepTheLightsOffPresenter? keepTheLightsOff = null, Release1TheEnvelopePresenter? theEnvelope = null) { _publisher = publisher ?? throw new ArgumentNullException("publisher"); _presenter = presenter ?? throw new ArgumentNullException("presenter"); _wrongAddress = wrongAddress; _roomWithNoName = roomWithNoName; _shortNotice = shortNotice; _keepTheLightsOff = keepTheLightsOff; _theEnvelope = theEnvelope; } public bool TryInvoke(Release1PresentationCommand command) { switch (command) { case Release1PresentationCommand.IntroAccept: return _publisher.TryAccept(); case Release1PresentationCommand.IntroDefer: return _publisher.TryDefer(); case Release1PresentationCommand.SmallCourtesyReview: return _presenter.TryReview().Status == Release1SmallCourtesyReviewStatus.Ready; case Release1PresentationCommand.SmallCourtesyAccept: return _presenter.TryAccept().Status == Release1SmallCourtesyDecisionStatus.Accepted; case Release1PresentationCommand.SmallCourtesyDefer: return _presenter.TryDefer().Status == Release1SmallCourtesyDecisionStatus.Deferred; case Release1PresentationCommand.WrongAddressReview: { Release1WrongAddressPresenter? wrongAddress2 = _wrongAddress; return wrongAddress2 != null && wrongAddress2.TryReview().Status == Release1WrongAddressReviewStatus.Ready; } case Release1PresentationCommand.WrongAddressAccept: { Release1WrongAddressPresenter? wrongAddress3 = _wrongAddress; return wrongAddress3 != null && wrongAddress3.TryAccept().Status == Release1WrongAddressDecisionStatus.Accepted; } case Release1PresentationCommand.WrongAddressDefer: { Release1WrongAddressPresenter? wrongAddress = _wrongAddress; return wrongAddress != null && wrongAddress.TryDefer().Status == Release1WrongAddressDecisionStatus.Deferred; } case Release1PresentationCommand.RoomWithNoNameReview: { Release1RoomWithNoNamePresenter? roomWithNoName2 = _roomWithNoName; return roomWithNoName2 != null && roomWithNoName2.TryReview().Status == Release1RoomWithNoNameReviewStatus.Ready; } case Release1PresentationCommand.RoomWithNoNameAccept: { Release1RoomWithNoNamePresenter? roomWithNoName3 = _roomWithNoName; return roomWithNoName3 != null && roomWithNoName3.TryAccept().Status == Release1RoomWithNoNameDecisionStatus.Accepted; } case Release1PresentationCommand.RoomWithNoNameDefer: { Release1RoomWithNoNamePresenter? roomWithNoName = _roomWithNoName; return roomWithNoName != null && roomWithNoName.TryDefer().Status == Release1RoomWithNoNameDecisionStatus.Deferred; } case Release1PresentationCommand.ShortNoticeReview: { Release1ShortNoticePresenter? shortNotice3 = _shortNotice; return shortNotice3 != null && shortNotice3.TryReview().Status == Release1ShortNoticeReviewStatus.Ready; } case Release1PresentationCommand.ShortNoticeAccept: { Release1ShortNoticePresenter? shortNotice2 = _shortNotice; return shortNotice2 != null && shortNotice2.TryAccept().Status == Release1ShortNoticeDecisionStatus.Accepted; } case Release1PresentationCommand.ShortNoticeDefer: { Release1ShortNoticePresenter? shortNotice = _shortNotice; return shortNotice != null && shortNotice.TryDefer().Status == Release1ShortNoticeDecisionStatus.Deferred; } case Release1PresentationCommand.KeepTheLightsOffReview: { Release1KeepTheLightsOffPresenter? keepTheLightsOff = _keepTheLightsOff; return keepTheLightsOff != null && keepTheLightsOff.TryReview().Status == Release1KeepTheLightsOffReviewStatus.Ready; } case Release1PresentationCommand.KeepTheLightsOffAccept: { Release1KeepTheLightsOffPresenter? keepTheLightsOff3 = _keepTheLightsOff; return keepTheLightsOff3 != null && keepTheLightsOff3.TryAccept().Status == Release1KeepTheLightsOffDecisionStatus.Accepted; } case Release1PresentationCommand.KeepTheLightsOffDefer: { Release1KeepTheLightsOffPresenter? keepTheLightsOff2 = _keepTheLightsOff; return keepTheLightsOff2 != null && keepTheLightsOff2.TryDefer().Status == Release1KeepTheLightsOffDecisionStatus.Deferred; } case Release1PresentationCommand.TheEnvelopeReview: { Release1TheEnvelopePresenter? theEnvelope3 = _theEnvelope; return theEnvelope3 != null && theEnvelope3.TryReview().Status == Release1TheEnvelopeReviewStatus.Ready; } case Release1PresentationCommand.TheEnvelopeAccept: { Release1TheEnvelopePresenter? theEnvelope2 = _theEnvelope; return theEnvelope2 != null && theEnvelope2.TryAccept().Status == Release1TheEnvelopeDecisionStatus.Accepted; } case Release1PresentationCommand.TheEnvelopeDefer: { Release1TheEnvelopePresenter? theEnvelope = _theEnvelope; return theEnvelope != null && theEnvelope.TryDefer().Status == Release1TheEnvelopeDecisionStatus.Deferred; } default: return false; } } } public enum Release1ProductionObservation { NotReady, Quiet, Working } public enum Release1ProductionHoldReason { None, WorldNotReady, OwnedPropertyCountMismatch } public sealed record Release1ProductionStationFingerprint(string StationGuid, Release1ProductionStationKind Kind, bool Running, string? OperationIdentity, int Progress); public sealed record Release1ProductionObservationResult(Release1ProductionObservation Observation, Release1ProductionHoldReason Reason, IReadOnlyList Stations); public static class Release1ProductionActivityClassifier { public static Release1ProductionObservationResult Classify(Release1ProductionActivitySnapshot? snapshot, Release1SmallCourtesyWorldReadStatus readStatus, int expectedOwnedPropertyCount, IReadOnlyList? previousStations, bool isBaselinePass) { if (expectedOwnedPropertyCount < 1) { throw new ArgumentOutOfRangeException("expectedOwnedPropertyCount"); } IReadOnlyList readOnlyList = previousStations ?? Array.Empty(); if (readStatus != Release1SmallCourtesyWorldReadStatus.Ready || (object)snapshot == null) { return new Release1ProductionObservationResult(Release1ProductionObservation.NotReady, Release1ProductionHoldReason.WorldNotReady, readOnlyList); } if (snapshot.Properties.Count != expectedOwnedPropertyCount) { return new Release1ProductionObservationResult(Release1ProductionObservation.NotReady, Release1ProductionHoldReason.OwnedPropertyCountMismatch, readOnlyList); } Release1ProductionStationFingerprint[] array = (from station in snapshot.Properties.SelectMany((Release1ProductionPropertySnapshot property) => property.Stations) select new Release1ProductionStationFingerprint(station.StationGuid, station.Kind, station.Running, station.OperationIdentity, station.Progress)).OrderBy((Release1ProductionStationFingerprint station) => station.StationGuid, StringComparer.Ordinal).ToArray(); if (snapshot.Properties.Any((Release1ProductionPropertySnapshot property) => property.Employees.Any((Release1ProductionEmployeeSnapshot employee) => (object)employee != null && !employee.Fired && employee.IsAnyWorkInProgress))) { return new Release1ProductionObservationResult(Release1ProductionObservation.Working, Release1ProductionHoldReason.None, array); } if (isBaselinePass) { return new Release1ProductionObservationResult(Release1ProductionObservation.Quiet, Release1ProductionHoldReason.None, array); } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); foreach (Release1ProductionStationFingerprint item in readOnlyList) { if ((object)item != null) { dictionary[item.StationGuid] = item; } } Release1ProductionStationFingerprint[] array2 = array; foreach (Release1ProductionStationFingerprint release1ProductionStationFingerprint in array2) { if (StartedSomethingNew(release1ProductionStationFingerprint, dictionary.TryGetValue(release1ProductionStationFingerprint.StationGuid, out var value) ? value : null)) { return new Release1ProductionObservationResult(Release1ProductionObservation.Working, Release1ProductionHoldReason.None, array); } } return new Release1ProductionObservationResult(Release1ProductionObservation.Quiet, Release1ProductionHoldReason.None, array); } public static Release1WindowObservation ToWindowObservation(Release1ProductionObservation observation) { return observation switch { Release1ProductionObservation.Quiet => Release1WindowObservation.Clean, Release1ProductionObservation.Working => Release1WindowObservation.Breached, _ => Release1WindowObservation.Unreadable, }; } private static bool StartedSomethingNew(Release1ProductionStationFingerprint now, Release1ProductionStationFingerprint? before) { if (!now.Running) { return false; } if ((object)before == null || !before.Running) { return true; } if (!string.Equals(now.OperationIdentity, before.OperationIdentity, StringComparison.Ordinal)) { return true; } return now.Progress < before.Progress; } } public enum Release1ProductionStationKind { ChemistryStation, LabOven, MixingStation, Cauldron, DryingRack } public sealed record Release1ProductionEmployeeSnapshot(string EmployeeId, string DisplayName, string Role, bool Fired, bool PaidForToday, bool IsWaitingOutside, int TicksSinceLastWork, bool IsAnyWorkInProgress, string? ActiveBehaviourName, string? ActiveBehaviourTypeName, bool ActiveBehaviourActive) { public void Validate() { if (string.IsNullOrWhiteSpace(EmployeeId)) { throw new ArgumentException("An employee needs an id.", "EmployeeId"); } if (string.IsNullOrWhiteSpace(DisplayName)) { throw new ArgumentException("An employee needs a display name.", "DisplayName"); } if (string.IsNullOrWhiteSpace(Role)) { throw new ArgumentException("An employee needs a role.", "Role"); } if (TicksSinceLastWork < 0) { throw new ArgumentOutOfRangeException("TicksSinceLastWork"); } } } public sealed record Release1ProductionStationSnapshot(string StationGuid, Release1ProductionStationKind Kind, bool Running, string? OperationIdentity, int Progress, int? CookTime = null, int? RemainingCookTime = null) { public void Validate() { if (string.IsNullOrWhiteSpace(StationGuid)) { throw new ArgumentException("A station needs a guid.", "StationGuid"); } if (!Enum.IsDefined(Kind)) { throw new ArgumentException("Station kind is not defined.", "Kind"); } if (Progress < 0) { throw new ArgumentOutOfRangeException("Progress"); } if (Running && string.IsNullOrWhiteSpace(OperationIdentity)) { throw new ArgumentException("A running station must report an operation identity.", "OperationIdentity"); } } } public sealed record Release1ProductionPropertySnapshot(string PropertyCode, string PropertyName, IReadOnlyList Employees, IReadOnlyList Stations) { public void Validate() { if (string.IsNullOrWhiteSpace(PropertyCode)) { throw new ArgumentException("A property needs a code.", "PropertyCode"); } if (string.IsNullOrWhiteSpace(PropertyName)) { throw new ArgumentException("A property needs a name.", "PropertyName"); } if (Employees == null) { throw new ArgumentNullException("Employees"); } if (Stations == null) { throw new ArgumentNullException("Stations"); } foreach (Release1ProductionEmployeeSnapshot employee in Employees) { (employee ?? throw new ArgumentException("Employees cannot be null.", "Employees")).Validate(); } foreach (Release1ProductionStationSnapshot station in Stations) { (station ?? throw new ArgumentException("Stations cannot be null.", "Stations")).Validate(); } if (Employees.Select((Release1ProductionEmployeeSnapshot employee) => employee.EmployeeId).Distinct(StringComparer.Ordinal).Count() != Employees.Count) { throw new ArgumentException("Employee ids must be unique within a property.", "Employees"); } if (Stations.Select((Release1ProductionStationSnapshot station) => station.StationGuid).Distinct(StringComparer.Ordinal).Count() != Stations.Count) { throw new ArgumentException("Station guids must be unique within a property.", "Stations"); } } } public sealed record Release1ProductionActivitySnapshot(IReadOnlyList Properties) { public static Release1ProductionActivitySnapshot Empty { get; } = new Release1ProductionActivitySnapshot(Array.Empty()); public void Validate() { if (Properties == null) { throw new ArgumentNullException("Properties"); } foreach (Release1ProductionPropertySnapshot property in Properties) { (property ?? throw new ArgumentException("Properties cannot be null.", "Properties")).Validate(); } if (Properties.Select((Release1ProductionPropertySnapshot property) => property.PropertyCode).Distinct(StringComparer.Ordinal).Count() != Properties.Count) { throw new ArgumentException("Property codes must be unique.", "Properties"); } string[] array = (from station in Properties.SelectMany((Release1ProductionPropertySnapshot property) => property.Stations) select station.StationGuid).ToArray(); if (array.Distinct(StringComparer.Ordinal).Count() != array.Length) { throw new ArgumentException("Station guids must be unique across every owned property.", "Properties"); } } [CompilerGenerated] private Release1ProductionActivitySnapshot(Release1ProductionActivitySnapshot original) { Properties = original.Properties; } } public static class Release1ProductionWorkBehaviours { public static IReadOnlyList WorkBehaviourTypeNames { get; } = new string[22] { "StartChemistryStationBehaviour", "StartLabOvenBehaviour", "FinishLabOvenBehaviour", "StartCauldronBehaviour", "StartMixingStationBehaviour", "PackagingStationBehaviour", "BrickPressBehaviour", "StartDryingRackBehaviour", "StopDryingRackBehaviour", "PickUpTrashBehaviour", "EmptyTrashGrabberBehaviour", "BagTrashCanBehaviour", "DisposeTrashBagBehaviour", "UseSpawnStationBehaviour", "AddSoilToGrowContainerBehaviour", "ApplyAdditiveToGrowContainerBehaviour", "SowSeedInPotBehaviour", "WaterPotBehaviour", "HarvestPotBehaviour", "MistMushroomBedBehaviour", "HarvestMushroomBedBehaviour", "ApplySpawnToMushroomBedBehaviour" }; public static bool ReadsAsWorking(Release1ProductionEmployeeSnapshot? employee) { if ((object)employee != null && employee.ActiveBehaviourActive) { string activeBehaviourTypeName = employee.ActiveBehaviourTypeName; if (activeBehaviourTypeName != null) { return WorkBehaviourTypeNames.Contains(activeBehaviourTypeName, StringComparer.Ordinal); } } return false; } } public static class Release1ProductionStationProgress { public const bool CauldronRemainingCountsDown = true; public static int CauldronProgress(int cookTime, int remainingCookTime, bool remainingCountsDown) { if (!remainingCountsDown) { return Math.Max(0, remainingCookTime); } return Math.Max(0, cookTime - remainingCookTime); } } public sealed class Release1ProductionComposition : IDisposable { private const string WrongAddressFallbackLog = "Wrong Address has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."; private const string RoomWithNoNameFallbackLog = "A Room With No Name has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."; private const string ShortNoticeFallbackLog = "Short Notice has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."; private const string KeepTheLightsOffFallbackLog = "Keep the Lights Off has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."; private const string TheEnvelopeFallbackLog = "The Envelope has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."; private readonly Release1StoryRuntimeService _story; private readonly Release1PresentationMode _mode; private readonly Action? _log; private readonly Func? _holdRoomMarker; private bool _loggedMissingHoldMarker; private bool _disposed; public Release1TransitionPublisherService Publisher { get; } public Release1PhoneCallService PhoneService { get; } public Release1IntroPromptPresenter PromptPresenter { get; } public Release1PayphoneBanner Banner { get; } public Release1SmallCourtesyComposition SmallCourtesy { get; } public Release1WrongAddressComposition WrongAddress { get; } public Release1RoomWithNoNameComposition RoomWithNoName { get; } public Release1ShortNoticeComposition ShortNotice { get; } public Release1KeepTheLightsOffComposition KeepTheLightsOff { get; } public Release1TheEnvelopeComposition TheEnvelope { get; } public Release1PresentationProjector? Projector { get; } public Release1DecisionPromptHost? DecisionPromptHost { get; } public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public Release1ProductionComposition(Release1StoryRuntimeService story, IRelease1PostBenziesUnlockReader reader, IRelease1PhoneCallQueue queue, IRelease1SmallCourtesyWorld smallCourtesyWorld, Action? log = null, Func? utcNow = null, Func? bannerNow = null, IRelease1DecisionPromptPlatform? promptPlatform = null, Release1PresentationMode mode = Release1PresentationMode.ImguiFallback, IRelease1NativePresentation? native = null, Func? holdRoomMarker = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(reader, "reader"); ArgumentNullException.ThrowIfNull(queue, "queue"); ArgumentNullException.ThrowIfNull(smallCourtesyWorld, "smallCourtesyWorld"); if (mode == Release1PresentationMode.Native && native == null) { throw new ArgumentException("A native presentation implementation is required in Native mode.", "native"); } _story = story; _mode = mode; _log = log; _holdRoomMarker = holdRoomMarker; Banner = new Release1PayphoneBanner(bannerNow); PhoneService = new Release1PhoneCallService(story, queue, Banner, log); Release1PhoneCallService phoneService = PhoneService; bool publishIntroCall = mode == Release1PresentationMode.ImguiFallback; Publisher = new Release1TransitionPublisherService(reader, story, phoneService, log, utcNow, null, null, publishIntroCall); PromptPresenter = new Release1IntroPromptPresenter(Publisher); SmallCourtesy = new Release1SmallCourtesyComposition(story, smallCourtesyWorld, log); WrongAddress = new Release1WrongAddressComposition(story, smallCourtesyWorld, PhoneService, log); RoomWithNoName = new Release1RoomWithNoNameComposition(story, smallCourtesyWorld, PhoneService, log); ShortNotice = new Release1ShortNoticeComposition(story, smallCourtesyWorld, PhoneService, log); KeepTheLightsOff = new Release1KeepTheLightsOffComposition(story, smallCourtesyWorld, PhoneService, log); TheEnvelope = new Release1TheEnvelopeComposition(story, smallCourtesyWorld, PhoneService, log); if (mode == Release1PresentationMode.Native) { Release1PresentationCommandRouter commands = new Release1PresentationCommandRouter(Publisher, SmallCourtesy.Presenter, WrongAddress.Presenter, RoomWithNoName.Presenter, ShortNotice.Presenter, KeepTheLightsOff.Presenter, TheEnvelope.Presenter); Projector = new Release1PresentationProjector(story, () => Release1PresentationPlanner.Build(BuildPresentationInputs()), native, commands, log); } else { DecisionPromptHost = new Release1DecisionPromptHost(() => PromptPresenter.DecisionPrompt ?? SmallCourtesy.Presenter.DecisionPrompt, promptPlatform ?? new Release1DecisionPromptUnityPlatform((KeyCode)288), log); } } public void OnLoadComplete() { if (!_disposed) { OrganizedCrimeTimingReceipts timing = Timing; timing.Measure("load-complete/production/publisher", Publisher.OnLoadComplete); timing.Measure("load-complete/production/small-courtesy", SmallCourtesy.OnLoadComplete); timing.Measure("load-complete/production/wrong-address", WrongAddress.OnLoadComplete); timing.Measure("load-complete/production/room-with-no-name", RoomWithNoName.OnLoadComplete); timing.Measure("load-complete/production/short-notice", ShortNotice.OnLoadComplete); timing.Measure("load-complete/production/keep-the-lights-off", KeepTheLightsOff.OnLoadComplete); timing.Measure("load-complete/production/the-envelope", TheEnvelope.OnLoadComplete); if (_mode == Release1PresentationMode.ImguiFallback) { _log?.Invoke("Wrong Address has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."); _log?.Invoke("A Room With No Name has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."); _log?.Invoke("Short Notice has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."); _log?.Invoke("Keep the Lights Off has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."); _log?.Invoke("The Envelope has no fallback presenter; the mission stays offered while the presentation mode is ImguiFallback."); } if (Projector != null) { timing.Measure("load-complete/production/projector", Projector.OnLoadComplete); } } } public void OnPreLoad() { if (!_disposed) { OrganizedCrimeTimingReceipts timing = Timing; DecisionPromptHost?.OnPreLoad(); if (Projector != null) { timing.Measure("pre-load/production/projector", Projector.OnPreLoad); } timing.Measure("pre-load/production/the-envelope", TheEnvelope.OnPreLoad); timing.Measure("pre-load/production/keep-the-lights-off", KeepTheLightsOff.OnPreLoad); timing.Measure("pre-load/production/short-notice", ShortNotice.OnPreLoad); timing.Measure("pre-load/production/room-with-no-name", RoomWithNoName.OnPreLoad); timing.Measure("pre-load/production/wrong-address", WrongAddress.OnPreLoad); timing.Measure("pre-load/production/small-courtesy", SmallCourtesy.OnPreLoad); timing.Measure("pre-load/production/publisher", Publisher.OnPreLoad); _loggedMissingHoldMarker = false; } } public void OnPreSceneChange() { if (!_disposed) { DecisionPromptHost?.OnPreSceneChange(); } } public void OnSaveStart() { if (!_disposed) { OrganizedCrimeTimingReceipts timing = Timing; timing.Measure("save-start/production/small-courtesy", SmallCourtesy.OnSaveStart); timing.Measure("save-start/production/wrong-address", WrongAddress.OnSaveStart); timing.Measure("save-start/production/room-with-no-name", RoomWithNoName.OnSaveStart); timing.Measure("save-start/production/short-notice", ShortNotice.OnSaveStart); timing.Measure("save-start/production/keep-the-lights-off", KeepTheLightsOff.OnSaveStart); timing.Measure("save-start/production/the-envelope", TheEnvelope.OnSaveStart); if (Projector != null) { timing.Measure("save-start/production/projector", Projector.OnSaveStart); } } } public void OnSaveComplete() { if (!_disposed) { OrganizedCrimeTimingReceipts timing = Timing; timing.Measure("save-complete/production/small-courtesy", SmallCourtesy.OnSaveComplete); timing.Measure("save-complete/production/wrong-address", WrongAddress.OnSaveComplete); timing.Measure("save-complete/production/room-with-no-name", RoomWithNoName.OnSaveComplete); timing.Measure("save-complete/production/short-notice", ShortNotice.OnSaveComplete); timing.Measure("save-complete/production/keep-the-lights-off", KeepTheLightsOff.OnSaveComplete); timing.Measure("save-complete/production/the-envelope", TheEnvelope.OnSaveComplete); if (Projector != null) { timing.Measure("save-complete/production/projector", Projector.OnSaveComplete); } } } public void Update() { if (!_disposed) { Publisher.Update(); SmallCourtesy.Update(); WrongAddress.Update(); RoomWithNoName.Update(); ShortNotice.Update(); KeepTheLightsOff.Update(); TheEnvelope.Update(); Projector?.Reconcile(); DecisionPromptHost?.Update(); Banner.Update(); } } public void OnGUI() { if (!_disposed) { DecisionPromptHost?.OnGUI(); Banner.OnGUI(); } } public void Dispose() { if (!_disposed) { _disposed = true; Projector?.Dispose(); DecisionPromptHost?.Dispose(); TheEnvelope.Dispose(); KeepTheLightsOff.Dispose(); ShortNotice.Dispose(); RoomWithNoName.Dispose(); WrongAddress.Dispose(); SmallCourtesy.Dispose(); Publisher.Dispose(); PhoneService.Dispose(); } } private Release1PresentationInputs BuildPresentationInputs() { Release1StoryState state = _story.State; Release1SmallCourtesyAssignment smallCourtesyAssignment = null; Release1WrongAddressAssignment wrongAddressAssignment = null; Release1WrongAddressProgress wrongAddressProgress = null; Release1RoomWithNoNameAssignment roomWithNoNameAssignment = null; Release1RoomWithNoNameProgress roomWithNoNameProgress = null; Release1ShortNoticeAssignment shortNoticeAssignment = null; Release1ShortNoticeProgress shortNoticeProgress = null; Release1KeepTheLightsOffAssignment keepTheLightsOffAssignment = null; Release1KeepTheLightsOffProgress keepTheLightsOffProgress = null; Release1TheEnvelopeAssignment theEnvelopeAssignment = null; Release1TheEnvelopeProgress theEnvelopeProgress = null; if ((object)state != null) { Release1MissionRecord smallCourtesy = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; smallCourtesyAssignment = (from candidate in state.SmallCourtesyAssignments where candidate.Attempt == smallCourtesy.Attempt orderby candidate.Attempt descending select candidate).FirstOrDefault(); Release1MissionRecord wrongAddress = state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; wrongAddressAssignment = state.WrongAddressAssignments.SingleOrDefault((Release1WrongAddressAssignment candidate) => candidate.Attempt == wrongAddress.Attempt); wrongAddressProgress = state.WrongAddressProgress.SingleOrDefault((Release1WrongAddressProgress candidate) => candidate.Attempt == wrongAddress.Attempt); Release1MissionRecord room = state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; roomWithNoNameAssignment = state.RoomWithNoNameAssignments.SingleOrDefault((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt == room.Attempt); roomWithNoNameProgress = state.RoomWithNoNameProgress.SingleOrDefault((Release1RoomWithNoNameProgress candidate) => candidate.Attempt == room.Attempt); Release1MissionRecord shortNotice = state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; shortNoticeAssignment = state.ShortNoticeAssignments.SingleOrDefault((Release1ShortNoticeAssignment candidate) => candidate.Attempt == shortNotice.Attempt); shortNoticeProgress = state.ShortNoticeProgress.SingleOrDefault((Release1ShortNoticeProgress candidate) => candidate.Attempt == shortNotice.Attempt); Release1MissionRecord keepTheLightsOff = state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; keepTheLightsOffAssignment = state.KeepTheLightsOffAssignments.SingleOrDefault((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == keepTheLightsOff.Attempt); keepTheLightsOffProgress = state.KeepTheLightsOffProgress.SingleOrDefault((Release1KeepTheLightsOffProgress candidate) => candidate.Attempt == keepTheLightsOff.Attempt); Release1MissionRecord theEnvelope = state.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; theEnvelopeAssignment = state.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == theEnvelope.Attempt); theEnvelopeProgress = state.TheEnvelopeProgress.SingleOrDefault((Release1TheEnvelopeProgress candidate) => candidate.Attempt == theEnvelope.Attempt); } return new Release1PresentationInputs(state, Publisher.PromptVisible, Publisher.EligibilityObserving, SmallCourtesy.Presenter.View, smallCourtesyAssignment, (_mode == Release1PresentationMode.Native) ? WrongAddress.Presenter.View : null, wrongAddressAssignment, wrongAddressProgress, (_mode == Release1PresentationMode.Native) ? RoomWithNoName.Presenter.View : null, roomWithNoNameAssignment, roomWithNoNameProgress, ReadHoldRoomMarker(), (_mode == Release1PresentationMode.Native) ? ShortNotice.Presenter.View : null, shortNoticeAssignment, shortNoticeProgress, (_mode == Release1PresentationMode.Native) ? KeepTheLightsOff.Presenter.View : null, keepTheLightsOffAssignment, keepTheLightsOffProgress, (_mode == Release1PresentationMode.Native) ? TheEnvelope.Presenter.View : null, theEnvelopeAssignment, theEnvelopeProgress); } private Release1WorldPoint? ReadHoldRoomMarker() { if (_holdRoomMarker == null) { return null; } try { Release1WorldPoint release1WorldPoint = _holdRoomMarker(); if ((object)release1WorldPoint == null) { if (!_loggedMissingHoldMarker) { _loggedMissingHoldMarker = true; _log?.Invoke("The Syndicate HQ door has not resolved yet; the hold objective is showing without a marker."); } return null; } return release1WorldPoint; } catch { return null; } } } public enum Release1QuietCensusObservation { NotReady, Clear, Present } public sealed record Release1QuietCensusDropSnapshot(string DropGuid, Release1SmallCourtesyWorldReadStatus SlotsStatus, IReadOnlyList Slots); public static class Release1QuietCensusClassifier { public static Release1QuietCensusObservation Classify(IReadOnlyList? drops, Release1HoldRoomSnapshot? room, string productId, int expectedDropCount, int expectedClosetCount) { Release1SmallCourtesyAssignment.ValidateStableId(productId, "productId"); if (expectedDropCount < 1) { throw new ArgumentOutOfRangeException("expectedDropCount"); } if (expectedClosetCount < 1) { throw new ArgumentOutOfRangeException("expectedClosetCount"); } if (drops == null || drops.Count != expectedDropCount) { return Release1QuietCensusObservation.NotReady; } if ((object)room == null || room.Readiness != Release1HoldRoomReadiness.Ready || room.Closets.Count != expectedClosetCount) { return Release1QuietCensusObservation.NotReady; } foreach (Release1QuietCensusDropSnapshot drop in drops) { if ((object)drop == null || drop.SlotsStatus != Release1SmallCourtesyWorldReadStatus.Ready || drop.Slots == null) { return Release1QuietCensusObservation.NotReady; } if (drop.Slots.Any((Release1SmallCourtesySlotSnapshot slot) => MatchesProduct(slot, productId))) { return Release1QuietCensusObservation.Present; } } foreach (Release1HoldRoomClosetSnapshot closet in room.Closets) { if ((object)closet == null) { return Release1QuietCensusObservation.NotReady; } if (closet.Slots.Any((Release1SmallCourtesySlotSnapshot slot) => MatchesProduct(slot, productId))) { return Release1QuietCensusObservation.Present; } } return Release1QuietCensusObservation.Clear; } private static bool MatchesProduct(Release1SmallCourtesySlotSnapshot? slot, string productId) { if ((object)slot != null && slot.Quantity > 0) { return string.Equals(slot.ProductId, productId, StringComparison.Ordinal); } return false; } } public enum Release1QuietDecision { Hold, ConfirmClear, ClearBreach, RecordBreach, FailWindow, Complete, None } public static class Release1QuietWindow { public const double BreachGraceGameMinutes = 60.0; public static Release1QuietDecision Decide(Release1QuietCensusObservation observation, Release1KeepTheLightsOffProgress progress, double nowGameMinutes, double windowDurationGameMinutes) { ArgumentNullException.ThrowIfNull(progress, "progress"); return Release1ConditionWindow.Decide(Observe(observation), new Release1WindowProgress(progress.ClearConfirmedAtGameMinutes, progress.BreachSincePassGameMinutes, Satisfied: false), nowGameMinutes, new Release1WindowProfile(windowDurationGameMinutes, 60.0, Release1WindowHealPolicy.HealBeforeElapse)) switch { Release1WindowDecision.OpenWindow => Release1QuietDecision.ConfirmClear, Release1WindowDecision.ClearBreach => Release1QuietDecision.ClearBreach, Release1WindowDecision.RecordBreach => Release1QuietDecision.RecordBreach, Release1WindowDecision.Elapsed => Release1QuietDecision.Complete, Release1WindowDecision.Fail => Release1QuietDecision.FailWindow, Release1WindowDecision.None => Release1QuietDecision.None, _ => Release1QuietDecision.Hold, }; } private static Release1WindowObservation Observe(Release1QuietCensusObservation observation) { return observation switch { Release1QuietCensusObservation.Clear => Release1WindowObservation.Clean, Release1QuietCensusObservation.Present => Release1WindowObservation.Breached, _ => Release1WindowObservation.Unreadable, }; } } public static class Release1RewardMath { private const decimal DefaultRewardMultiplier = 1.25m; public static float? QuoteWholeDollars(float deliveredValue) { return QuoteWholeDollars(deliveredValue, 1.25m); } public static float? QuoteWholeDollars(float deliveredValue, decimal multiplier) { if (!float.IsFinite(deliveredValue) || deliveredValue < 0f || multiplier <= 0m) { return null; } try { return (float)Math.Round((decimal)deliveredValue * multiplier, 0, MidpointRounding.AwayFromZero); } catch (OverflowException) { return null; } } } public sealed class Release1RoomWithNoNameComposition : IDisposable { private bool _disposed; public Release1RoomWithNoNameMissionService Service { get; } public Release1RoomWithNoNamePresenter Presenter { get; } public Release1RoomWithNoNameComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService phone, Action? log = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(world, "world"); ArgumentNullException.ThrowIfNull(phone, "phone"); Service = new Release1RoomWithNoNameMissionService(story, world, phone, log); Presenter = new Release1RoomWithNoNamePresenter(Service, story, log); } public void OnLoadComplete() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnLoadComplete(); } } public void OnPreLoad() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnPreLoad(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); } } public void Update() { if (!_disposed) { Service.Update(); } } public void Dispose() { if (!_disposed) { _disposed = true; Presenter.Dispose(); Service.Dispose(); } } } public enum Release1RoomWithNoNameOfferStatus { Inactive, Available, Ineligible, Pending, Saving, NoDiscoveredProduct, InsufficientEmptyDeadDrops, Unavailable, Dismissed, Disposed } public enum Release1RoomWithNoNameReviewStatus { Ready, Inactive, Ineligible, Pending, Saving, NoDiscoveredProduct, InsufficientEmptyDeadDrops, Unavailable, Dismissed, Disposed } public enum Release1RoomWithNoNameDecisionStatus { Accepted, Deferred, Dismissed, ReviewRequired, QuoteChanged, InsufficientEmptyDeadDrops, PersistenceDeferred, Rejected, Inactive, Disposed } public enum Release1RoomWithNoNameStageStatus { NoWork, Staged, AlreadyStaged, CustodyInferred, Held, Unavailable, Rejected } public enum Release1RoomWithNoNameHoldStatus { NoWork, RoomNotReady, Stowed, Holding, Released, Missing, Failed, Ambiguous, Rejected } public enum Release1RoomWithNoNameDeliveryStatus { NoWork, Paid, AwaitingAppliedSave, Committed, Ambiguous, Rejected } public sealed record Release1RoomWithNoNameQuote(Release1RoomWithNoNameAssignment Assignment, double? DeadlineDurationHours) { public void Validate() { Assignment.Validate(); if (Assignment.Mode == Release1RoomWithNoNameAssignmentMode.Recovery) { if (DeadlineDurationHours.HasValue) { throw new ArgumentException("Recovery quotes cannot have a deadline.", "DeadlineDurationHours"); } } else if (DeadlineDurationHours.GetValueOrDefault() != 72.0) { throw new ArgumentException("Timed Room With No Name stages require a 72-hour duration.", "DeadlineDurationHours"); } } } public sealed record Release1RoomWithNoNameReviewResult(Release1RoomWithNoNameReviewStatus Status, Release1RoomWithNoNameQuote? Quote, string Message); public sealed record Release1RoomWithNoNameDecisionResult(Release1RoomWithNoNameDecisionStatus Status, string Message); public static class Release1RoomWithNoNameHoldRoomHarness { public static Release1StagingHarnessResult TryDump(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); try { Release1HoldRoomSnapshot room; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadHoldRoom(out room); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || (object)room == null) { list.Add($"the hold room could not be read, status {release1SmallCourtesyWorldReadStatus}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), list); } list.Add($"hold room readiness: {room.Readiness}, closets {room.Closets.Count}"); if (room.Readiness != Release1HoldRoomReadiness.Ready) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } foreach (Release1HoldRoomClosetSnapshot item in room.Closets.OrderBy((Release1HoldRoomClosetSnapshot candidate) => candidate.ClosetGuid, StringComparer.Ordinal)) { int value = item.Slots.Count((Release1SmallCourtesySlotSnapshot slot) => slot.Quantity > 0); list.Add($"closet {item.ClosetGuid} slots {item.SlotCount}, occupied {value}"); foreach (Release1SmallCourtesySlotSnapshot item2 in from candidate in item.Slots where candidate.Quantity > 0 orderby candidate.SlotIndex select candidate) { list.Add($"closet {item.ClosetGuid} slot {item2.SlotIndex} product {item2.ProductId} packaging {item2.PackagingId} quantity {item2.Quantity} value {item2.MonetaryValue}"); } } return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex) { list.Add("an exception was thrown while dumping the hold room: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } private static Release1StagingHarnessStatus MapReadStatus(Release1SmallCourtesyWorldReadStatus status) { return status switch { Release1SmallCourtesyWorldReadStatus.NotAuthoritative => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldReadStatus.Pending => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } } public sealed class Release1RoomWithNoNameMissionService : IDisposable { private const string TermsVersion = "room-with-no-name-v1"; private const double TimedStageDurationHours = 72.0; private const string EffectScope = "oc-room-with-no-name"; private const string RewardDestination = "player-cash"; private const string ArthurCallerLabel = "Arthur Selby"; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1PhoneCallService? _phone; private readonly Action _log; private bool _loadActive; private bool _saving; private bool _dismissed; private bool _disposed; private Release1RoomWithNoNameQuote? _reviewedQuote; private readonly HashSet _reportedHolds = new HashSet(StringComparer.Ordinal); private IRelease1SmallCourtesyDropSubscription? _dropSubscription; private string? _boundDropGuid; private string? _lockedDropGuid; private int _lockedSlotIndex = -1; private static readonly string[] ArthurWarningStages = new string[3] { "I am told the room is fucking empty and the day is not up. You don't want a visit from me. Fix it.", "Nell has arranged one more run. You have seventy two hours from the moment you accept it. She's a lot more fucking forgiving than I am, don't fuck this up.", "Do not make her ask twice." }; public Release1RoomWithNoNameOfferStatus OfferStatus { get; private set; } public Release1RoomWithNoNameQuote? ReviewedQuote => _reviewedQuote; public Release1RoomWithNoNameMissionService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService? phone = null, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _phone = phone; _log = log ?? ((Action)delegate { }); } public void OnLoadComplete() { if (!_disposed && !_loadActive) { _loadActive = true; _saving = false; _dismissed = false; _reviewedQuote = null; _reportedHolds.Clear(); RefreshOfferStatus(); Converge(); } } public void OnPreLoad() { if (!_disposed) { ReleaseSlotLock(); DisposeDropSubscription(); _loadActive = false; _saving = false; _dismissed = false; _reviewedQuote = null; _reportedHolds.Clear(); OfferStatus = Release1RoomWithNoNameOfferStatus.Inactive; } } public void OnSaveStart() { if (!_disposed && _loadActive) { _saving = true; OfferStatus = Release1RoomWithNoNameOfferStatus.Saving; } } public void OnSaveComplete() { if (!_disposed && _loadActive) { _saving = false; RefreshOfferStatus(); Converge(); } } public void Update() { if (_disposed || !_loadActive || _saving) { return; } Release1StoryHostContextSnapshot context; Release1RoomWithNoNameReviewStatus release1RoomWithNoNameReviewStatus = ReadMatchingContext(out context); if (release1RoomWithNoNameReviewStatus != Release1RoomWithNoNameReviewStatus.Ready) { OfferStatus = Map(release1RoomWithNoNameReviewStatus); return; } Release1StoryState state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Accepted) { Release1MissionRecord release1MissionRecord = Mission(state); Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive) ? true : false); if (flag && release1MissionRecord.DeadlineGameTimeHours.HasValue && TryReadGameHours(out var gameHours) && gameHours >= release1MissionRecord.DeadlineGameTimeHours.Value && !HasConsumptionEffect(release1MissionRecord.Attempt)) { Release1TransitionKind transition = ((release1MissionRecord.State == Release1MissionState.Active) ? Release1TransitionKind.RequiredFailure : Release1TransitionKind.MakeGoodFailed); string receipt = $"room-with-no-name-deadline-{DeadlineToken(release1MissionRecord.State)}-v1-a{release1MissionRecord.Attempt}"; _story.TryExecuteDurably(CreateCommand(transition, release1MissionRecord.Attempt, receipt, null, null, null)); RefreshOfferStatus(); } Converge(); } } public Release1RoomWithNoNameReviewResult TryReview() { if (_disposed) { return Review(Release1RoomWithNoNameReviewStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Review(Release1RoomWithNoNameReviewStatus.Inactive, "No load epoch is active."); } if (_saving) { return Review(Release1RoomWithNoNameReviewStatus.Saving, "Mission review is deferred during saving."); } if (_dismissed) { return Review(Release1RoomWithNoNameReviewStatus.Dismissed, "Offer was dismissed for this load."); } Release1RoomWithNoNameQuote quote; Release1RoomWithNoNameReviewStatus release1RoomWithNoNameReviewStatus = TryBuildQuote(out quote); if (release1RoomWithNoNameReviewStatus != Release1RoomWithNoNameReviewStatus.Ready) { _reviewedQuote = null; OfferStatus = Map(release1RoomWithNoNameReviewStatus); return Review(release1RoomWithNoNameReviewStatus, Message(release1RoomWithNoNameReviewStatus)); } _reviewedQuote = quote; OfferStatus = Release1RoomWithNoNameOfferStatus.Available; return new Release1RoomWithNoNameReviewResult(Release1RoomWithNoNameReviewStatus.Ready, quote, "Room With No Name terms are ready for review."); } public Release1RoomWithNoNameDecisionResult TryAccept() { if (_disposed) { return Decision(Release1RoomWithNoNameDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _dismissed) { return Decision(Release1RoomWithNoNameDecisionStatus.Inactive, "No active offer is available."); } if (_saving) { return Decision(Release1RoomWithNoNameDecisionStatus.PersistenceDeferred, "Acceptance is deferred during saving."); } if ((object)_reviewedQuote == null) { return Decision(Release1RoomWithNoNameDecisionStatus.ReviewRequired, "Review the current terms before accepting."); } Release1RoomWithNoNameQuote quote; Release1RoomWithNoNameReviewStatus release1RoomWithNoNameReviewStatus = TryBuildQuote(out quote); switch (release1RoomWithNoNameReviewStatus) { case Release1RoomWithNoNameReviewStatus.InsufficientEmptyDeadDrops: _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1RoomWithNoNameDecisionStatus.InsufficientEmptyDeadDrops, Message(release1RoomWithNoNameReviewStatus)); case Release1RoomWithNoNameReviewStatus.Ready: if (!(quote != _reviewedQuote)) { if (!TryReadGameHours(out var gameHours)) { return Decision(Release1RoomWithNoNameDecisionStatus.Rejected, "Canonical game time was unavailable."); } Release1RoomWithNoNameAssignment assignment = quote.Assignment; Release1StoryCommand command = CreateCommand(assignment.Mode switch { Release1RoomWithNoNameAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1RoomWithNoNameAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1RoomWithNoNameAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }, assignment.Attempt, AcceptanceReceipt(assignment.Mode, assignment.Attempt), (assignment.Mode == Release1RoomWithNoNameAssignmentMode.Primary) ? "room-with-no-name-v1" : null, gameHours, (assignment.Mode == Release1RoomWithNoNameAssignmentMode.Recovery) ? ((double?)null) : new double?(gameHours + 72.0)); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteRoomWithNoNameAcceptanceDurably(command, assignment); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1RoomWithNoNameDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1RoomWithNoNameDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } _reviewedQuote = null; RefreshOfferStatus(); Converge(); return Decision(Release1RoomWithNoNameDecisionStatus.Accepted, "Room With No Name stage was accepted."); } goto default; default: _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1RoomWithNoNameDecisionStatus.QuoteChanged, "The available product, package, or drops changed; review again."); } } public Release1RoomWithNoNameDecisionResult TryDefer() { if (_disposed) { return Decision(Release1RoomWithNoNameDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _saving || ReadMatchingContext(out var _) != Release1RoomWithNoNameReviewStatus.Ready) { return Decision(Release1RoomWithNoNameDecisionStatus.Inactive, "No active offer is available."); } Release1StoryState state = _story.State; Release1MissionRecord release1MissionRecord = (((object)state == null) ? null : Mission(state)); if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted || (object)release1MissionRecord == null || release1MissionRecord.State != Release1MissionState.Offered) { return TryDismiss(); } string receipt = $"room-with-no-name-defer-v1-a{release1MissionRecord.Attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(Release1TransitionKind.MissionDeferred, release1MissionRecord.Attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1RoomWithNoNameDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1RoomWithNoNameDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } DismissForLoad(); return Decision(Release1RoomWithNoNameDecisionStatus.Deferred, "Room With No Name was deferred until a later load."); } public Release1RoomWithNoNameDecisionResult TryDismiss() { if (_disposed) { return Decision(Release1RoomWithNoNameDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Decision(Release1RoomWithNoNameDecisionStatus.Inactive, "No load epoch is active."); } DismissForLoad(); return Decision(Release1RoomWithNoNameDecisionStatus.Dismissed, "Room With No Name was dismissed for this load."); } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; OfferStatus = Release1RoomWithNoNameOfferStatus.Disposed; } } private Release1RoomWithNoNameReviewStatus TryBuildQuote(out Release1RoomWithNoNameQuote? quote) { try { return TryBuildQuoteCore(out quote); } catch { quote = null; return Release1RoomWithNoNameReviewStatus.Unavailable; } } private Release1RoomWithNoNameReviewStatus TryBuildQuoteCore(out Release1RoomWithNoNameQuote? quote) { quote = null; Release1StoryHostContextSnapshot context; Release1RoomWithNoNameReviewStatus release1RoomWithNoNameReviewStatus = ReadMatchingContext(out context); if (release1RoomWithNoNameReviewStatus != Release1RoomWithNoNameReviewStatus.Ready) { return release1RoomWithNoNameReviewStatus; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1RoomWithNoNameReviewStatus.Ineligible; } if (state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")].State != Release1MissionState.Satisfied) { return Release1RoomWithNoNameReviewStatus.Ineligible; } Release1MissionRecord release1MissionRecord = Mission(state); Release1RoomWithNoNameAssignmentMode? release1RoomWithNoNameAssignmentMode = release1MissionRecord.State switch { Release1MissionState.Offered => Release1RoomWithNoNameAssignmentMode.Primary, Release1MissionState.MakeGoodOffered => Release1RoomWithNoNameAssignmentMode.MakeGood, Release1MissionState.RecoveryAvailable => Release1RoomWithNoNameAssignmentMode.Recovery, _ => null, }; if (!release1RoomWithNoNameAssignmentMode.HasValue) { return Release1RoomWithNoNameReviewStatus.Ineligible; } int attempt = ((release1RoomWithNoNameAssignmentMode == Release1RoomWithNoNameAssignmentMode.Primary) ? release1MissionRecord.Attempt : (release1MissionRecord.Attempt + 1)); Release1TransitionKind transitionKind = release1RoomWithNoNameAssignmentMode switch { Release1RoomWithNoNameAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1RoomWithNoNameAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1RoomWithNoNameAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string value = Release1LogicalCorrelation.Create(context.PlayerId, "release1.room-with-no-name", attempt, transitionKind, AcceptanceReceipt(release1RoomWithNoNameAssignmentMode.Value, attempt)).Value; Release1SmallCourtesyPackageKind kind = ((release1RoomWithNoNameAssignmentMode != Release1RoomWithNoNameAssignmentMode.Primary) ? Release1SmallCourtesyPackageKind.Jar : Release1SmallCourtesyPackageKind.Brick); if (_world.TryReadPackaging(kind, out Release1SmallCourtesyPackagingCandidate packaging) != Release1SmallCourtesyWorldReadStatus.Ready) { return Release1RoomWithNoNameReviewStatus.Unavailable; } try { packaging.Validate(); } catch (ArgumentException) { return Release1RoomWithNoNameReviewStatus.Unavailable; } IReadOnlyList products; if (release1RoomWithNoNameAssignmentMode == Release1RoomWithNoNameAssignmentMode.Primary) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadProducts(out products); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } } else { Release1RoomWithNoNameAssignment release1RoomWithNoNameAssignment = state.RoomWithNoNameAssignments.OrderBy((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt).FirstOrDefault(); if ((object)release1RoomWithNoNameAssignment == null) { return Release1RoomWithNoNameReviewStatus.Ineligible; } products = new Release1SmallCourtesyProductCandidate[1] { new Release1SmallCourtesyProductCandidate(release1RoomWithNoNameAssignment.ProductId, release1RoomWithNoNameAssignment.ProductName, 0.0, IsDiscovered: true) }; } IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = _world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus2); } if (!Release1RoomWithNoNameAssignmentSelector.TrySelect(products, drops, release1RoomWithNoNameAssignmentMode.Value, attempt, value, packaging.PackagingId, packaging.PackagingName, out Release1RoomWithNoNameAssignment assignment, out Release1RoomWithNoNameSelectionStatus status)) { return status switch { Release1RoomWithNoNameSelectionStatus.NoDiscoveredProduct => Release1RoomWithNoNameReviewStatus.NoDiscoveredProduct, Release1RoomWithNoNameSelectionStatus.InsufficientEmptyDeadDrops => Release1RoomWithNoNameReviewStatus.InsufficientEmptyDeadDrops, _ => Release1RoomWithNoNameReviewStatus.Unavailable, }; } quote = new Release1RoomWithNoNameQuote(assignment, (release1RoomWithNoNameAssignmentMode == Release1RoomWithNoNameAssignmentMode.Recovery) ? ((double?)null) : new double?(72.0)); quote.Validate(); return Release1RoomWithNoNameReviewStatus.Ready; } private void RefreshOfferStatus() { if (_disposed) { OfferStatus = Release1RoomWithNoNameOfferStatus.Disposed; } else if (!_loadActive) { OfferStatus = Release1RoomWithNoNameOfferStatus.Inactive; } else if (_saving) { OfferStatus = Release1RoomWithNoNameOfferStatus.Saving; } else if (_dismissed) { OfferStatus = Release1RoomWithNoNameOfferStatus.Dismissed; } else { OfferStatus = Map(TryBuildQuote(out Release1RoomWithNoNameQuote _)); } } private Release1RoomWithNoNameReviewStatus ReadMatchingContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1RoomWithNoNameReviewStatus.Inactive; } try { Release1StoryHostContextSnapshot context2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadContext(out context2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!SameContext(snapshot, context2)) { return Release1RoomWithNoNameReviewStatus.Inactive; } context = snapshot; return Release1RoomWithNoNameReviewStatus.Ready; } catch { return Release1RoomWithNoNameReviewStatus.Unavailable; } } private bool TryReadGameHours(out double gameHours) { gameHours = 0.0; if (!TryReadGameMinutes(out var totalMinutes)) { return false; } gameHours = totalMinutes / 60.0; return double.IsFinite(gameHours); } private bool TryReadGameMinutes(out double totalMinutes) { totalMinutes = 0.0; try { if (_world.TryReadCanonicalTotalMinutes(out var totalMinutes2) != Release1SmallCourtesyWorldReadStatus.Ready || !double.IsFinite(totalMinutes2) || totalMinutes2 < 0.0) { return false; } totalMinutes = totalMinutes2; return true; } catch { return false; } } private Release1StoryCommand CreateCommand(Release1TransitionKind transition, int attempt, string receipt, string? termsVersion, double? acceptedHours, double? deadlineHours) { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { throw new InvalidOperationException("Story context was not active."); } return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, "release1.room-with-no-name", attempt, transition, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.room-with-no-name", attempt, transition, receipt).Value, termsVersion, acceptedHours, deadlineHours); } private void DismissForLoad() { _dismissed = true; _reviewedQuote = null; OfferStatus = Release1RoomWithNoNameOfferStatus.Dismissed; } private static Release1MissionRecord Mission(Release1StoryState state) { return state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; } private static string AcceptanceReceipt(Release1RoomWithNoNameAssignmentMode mode, int attempt) { return $"room-with-no-name-{mode.ToString().ToLowerInvariant()}-accept-v1-a{attempt}"; } private static string DeadlineToken(Release1MissionState state) { if (state != Release1MissionState.Active) { return "make-good"; } return "primary"; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private static Release1RoomWithNoNameReviewResult Review(Release1RoomWithNoNameReviewStatus status, string message) { return new Release1RoomWithNoNameReviewResult(status, null, message); } private static Release1RoomWithNoNameDecisionResult Decision(Release1RoomWithNoNameDecisionStatus status, string message) { return new Release1RoomWithNoNameDecisionResult(status, message); } private static Release1RoomWithNoNameReviewStatus Map(Release1SmallCourtesyWorldReadStatus status) { if ((uint)(status - 1) <= 1u) { return Release1RoomWithNoNameReviewStatus.Pending; } return Release1RoomWithNoNameReviewStatus.Unavailable; } private static Release1RoomWithNoNameOfferStatus Map(Release1RoomWithNoNameReviewStatus status) { return status switch { Release1RoomWithNoNameReviewStatus.Ready => Release1RoomWithNoNameOfferStatus.Available, Release1RoomWithNoNameReviewStatus.Inactive => Release1RoomWithNoNameOfferStatus.Inactive, Release1RoomWithNoNameReviewStatus.Ineligible => Release1RoomWithNoNameOfferStatus.Ineligible, Release1RoomWithNoNameReviewStatus.Pending => Release1RoomWithNoNameOfferStatus.Pending, Release1RoomWithNoNameReviewStatus.Saving => Release1RoomWithNoNameOfferStatus.Saving, Release1RoomWithNoNameReviewStatus.NoDiscoveredProduct => Release1RoomWithNoNameOfferStatus.NoDiscoveredProduct, Release1RoomWithNoNameReviewStatus.InsufficientEmptyDeadDrops => Release1RoomWithNoNameOfferStatus.InsufficientEmptyDeadDrops, Release1RoomWithNoNameReviewStatus.Dismissed => Release1RoomWithNoNameOfferStatus.Dismissed, Release1RoomWithNoNameReviewStatus.Disposed => Release1RoomWithNoNameOfferStatus.Disposed, _ => Release1RoomWithNoNameOfferStatus.Unavailable, }; } private static string Message(Release1RoomWithNoNameReviewStatus status) { return status switch { Release1RoomWithNoNameReviewStatus.NoDiscoveredProduct => "No discovered product is available for assignment.", Release1RoomWithNoNameReviewStatus.InsufficientEmptyDeadDrops => "Two clear dead drops are required before a hold can be assigned.", Release1RoomWithNoNameReviewStatus.Pending => "World eligibility is still pending.", Release1RoomWithNoNameReviewStatus.Ineligible => "Room With No Name is not currently offerable.", Release1RoomWithNoNameReviewStatus.Inactive => "The canonical story context is inactive.", _ => "Room With No Name world data is unavailable.", }; } public Release1RoomWithNoNameStageStatus ReconcileStaging() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1RoomWithNoNameReviewStatus.Ready) { return Release1RoomWithNoNameStageStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord mission, out Release1RoomWithNoNameAssignment assignment, out string _)) { return Release1RoomWithNoNameStageStatus.NoWork; } Release1RoomWithNoNameProgress release1RoomWithNoNameProgress = Progress(assignment.Attempt); if ((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress.Custody) { return Release1RoomWithNoNameStageStatus.NoWork; } if (HasConsumptionEffect(mission.Attempt)) { return Release1RoomWithNoNameStageStatus.NoWork; } if (!TryReadSlots(assignment.SourceDropGuid, out IReadOnlyList slots)) { return Release1RoomWithNoNameStageStatus.Unavailable; } Release1ConsignmentFingerprint fingerprint = Fingerprint(assignment); int matchedSlotIndex; switch (Release1ConsignmentClassifier.Classify(slots, fingerprint, out matchedSlotIndex)) { case Release1ContainerObservation.ZeroLength: ReportHoldOnce(assignment.Attempt, "the pick up drop read back with zero slots; a zero-length read is not treated as empty."); return Release1RoomWithNoNameStageStatus.Unavailable; case Release1ContainerObservation.ExactlyOne: if ((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress.Staged) { return Release1RoomWithNoNameStageStatus.AlreadyStaged; } if (!SetProgress(assignment.Attempt, true)) { return Release1RoomWithNoNameStageStatus.Rejected; } return Release1RoomWithNoNameStageStatus.AlreadyStaged; default: ReportHoldOnce(assignment.Attempt, "the pick up drop holds something other than exactly the staged consignment; Room With No Name will not mutate it."); return Release1RoomWithNoNameStageStatus.Held; case Release1ContainerObservation.Empty: { if ((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress.Staged) { if (!SetProgress(assignment.Attempt, true, true)) { return Release1RoomWithNoNameStageStatus.Rejected; } return Release1RoomWithNoNameStageStatus.CustodyInferred; } Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; string reason; try { release1SmallCourtesyWorldMutationStatus = _world.TryInsertPackagedProduct(assignment.SourceDropGuid, 0, assignment.ProductId, assignment.PackagingId, assignment.PackageQuantity, out reason); } catch (Exception ex) { ReportHoldOnce(assignment.Attempt, "staging threw " + ex.GetType().Name + "; Room With No Name will not retry blindly."); return Release1RoomWithNoNameStageStatus.Held; } switch (release1SmallCourtesyWorldMutationStatus) { case Release1SmallCourtesyWorldMutationStatus.Unavailable: return Release1RoomWithNoNameStageStatus.Unavailable; default: ReportHoldOnce(assignment.Attempt, "staging was refused: " + reason); return Release1RoomWithNoNameStageStatus.Held; case Release1SmallCourtesyWorldMutationStatus.Succeeded: { if (!TryReadSlots(assignment.SourceDropGuid, out IReadOnlyList slots2) || Release1ConsignmentClassifier.Classify(slots2, fingerprint, out matchedSlotIndex) != Release1ContainerObservation.ExactlyOne) { ReportHoldOnce(assignment.Attempt, "the staged consignment did not read back as the exact single fingerprint in the drop."); return Release1RoomWithNoNameStageStatus.Held; } if (!SetProgress(assignment.Attempt, true)) { return Release1RoomWithNoNameStageStatus.Rejected; } return Release1RoomWithNoNameStageStatus.Staged; } } } } } public Release1RoomWithNoNameStageStatus TryHandleDropClosed(string deadDropGuid) { if (_disposed || !_loadActive || _saving) { return Release1RoomWithNoNameStageStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord _, out Release1RoomWithNoNameAssignment assignment, out string _)) { return Release1RoomWithNoNameStageStatus.NoWork; } Release1RoomWithNoNameProgress release1RoomWithNoNameProgress = Progress(assignment.Attempt); string b = (((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress.HoldSatisfied) ? assignment.HandoffDropGuid : assignment.SourceDropGuid); if (!string.Equals(deadDropGuid, b, StringComparison.Ordinal)) { return Release1RoomWithNoNameStageStatus.NoWork; } Release1RoomWithNoNameStageStatus result = Converge(); if ((object)release1RoomWithNoNameProgress == null || !release1RoomWithNoNameProgress.Custody) { return result; } return Release1RoomWithNoNameStageStatus.NoWork; } public Release1RoomWithNoNameHoldStatus ReconcileHold() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1RoomWithNoNameReviewStatus.Ready) { return Release1RoomWithNoNameHoldStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord mission, out Release1RoomWithNoNameAssignment assignment, out string authorization)) { return Release1RoomWithNoNameHoldStatus.NoWork; } if (HasConsumptionEffect(assignment.Attempt)) { return Release1RoomWithNoNameHoldStatus.NoWork; } Release1RoomWithNoNameProgress release1RoomWithNoNameProgress = Progress(assignment.Attempt); if ((object)release1RoomWithNoNameProgress == null || !release1RoomWithNoNameProgress.Custody) { return Release1RoomWithNoNameHoldStatus.NoWork; } if (!TryReadGameMinutes(out var totalMinutes)) { return Release1RoomWithNoNameHoldStatus.NoWork; } Release1HoldRoomSnapshot room = null; try { if (_world.TryReadHoldRoom(out Release1HoldRoomSnapshot room2) == Release1SmallCourtesyWorldReadStatus.Ready) { room = room2; } } catch { room = null; } string holdingClosetGuid; Release1HoldObservation release1HoldObservation = Release1HoldRoomClassifier.Classify(room, Fingerprint(assignment), assignment.ExpectedClosetCount, out holdingClosetGuid); switch (Release1HoldWindow.Decide(release1HoldObservation, release1RoomWithNoNameProgress, totalMinutes, assignment.HoldDurationGameMinutes)) { case Release1HoldDecision.RecordStow: { if (holdingClosetGuid == null) { return Release1RoomWithNoNameHoldStatus.RoomNotReady; } int attempt2 = assignment.Attempt; bool? stowed = true; double? stowedAtGameMinutes = totalMinutes; authorization = holdingClosetGuid; if (!SetProgress(attempt2, null, null, stowed, null, stowedAtGameMinutes, authorization, null, clearMissingSince: true)) { return Release1RoomWithNoNameHoldStatus.Rejected; } return Release1RoomWithNoNameHoldStatus.Stowed; } case Release1HoldDecision.SatisfyHold: { int attempt5 = assignment.Attempt; bool? stowed = true; authorization = holdingClosetGuid; if (!SetProgress(attempt5, null, null, null, stowed, null, authorization, null, clearMissingSince: true)) { return Release1RoomWithNoNameHoldStatus.Rejected; } return Release1RoomWithNoNameHoldStatus.Released; } case Release1HoldDecision.ClearMissing: { int attempt3 = assignment.Attempt; authorization = holdingClosetGuid; if (!SetProgress(attempt3, null, null, null, null, null, authorization, null, clearMissingSince: true)) { return Release1RoomWithNoNameHoldStatus.Rejected; } return Release1RoomWithNoNameHoldStatus.Holding; } case Release1HoldDecision.RecordMissing: { int attempt4 = assignment.Attempt; double? stowedAtGameMinutes = totalMinutes; if (!SetProgress(attempt4, null, null, null, null, null, null, stowedAtGameMinutes)) { return Release1RoomWithNoNameHoldStatus.Rejected; } return Release1RoomWithNoNameHoldStatus.Missing; } case Release1HoldDecision.FailHold: return FailHold(mission, assignment.Attempt); case Release1HoldDecision.Hold: switch (release1HoldObservation) { case Release1HoldObservation.RoomNotReady: ReportHoldOnce(assignment.Attempt, "the hold room was not readable this pass; the hold is unchanged."); return Release1RoomWithNoNameHoldStatus.RoomNotReady; case Release1HoldObservation.Ambiguous: ReportHoldOnce(assignment.Attempt, "the hold room holds more than one matching consignment; the hold is unchanged."); return Release1RoomWithNoNameHoldStatus.Ambiguous; default: return Release1RoomWithNoNameHoldStatus.Missing; } default: if (release1HoldObservation != Release1HoldObservation.Held) { return Release1RoomWithNoNameHoldStatus.NoWork; } if (holdingClosetGuid != null && !string.Equals(release1RoomWithNoNameProgress.HoldingClosetGuid, holdingClosetGuid, StringComparison.Ordinal)) { int attempt = assignment.Attempt; authorization = holdingClosetGuid; if (!SetProgress(attempt, null, null, null, null, null, authorization)) { return Release1RoomWithNoNameHoldStatus.Rejected; } } if (!release1RoomWithNoNameProgress.HoldSatisfied) { return Release1RoomWithNoNameHoldStatus.Holding; } return Release1RoomWithNoNameHoldStatus.NoWork; } } private Release1RoomWithNoNameHoldStatus FailHold(Release1MissionRecord mission, int attempt) { var (transition, text) = mission.State switch { Release1MissionState.Active => (Release1TransitionKind.RequiredFailure, "primary"), Release1MissionState.MakeGoodActive => (Release1TransitionKind.MakeGoodFailed, "make-good"), Release1MissionState.RecoveryActive => (Release1TransitionKind.RecoveryFailed, "recovery"), _ => (Release1TransitionKind.RequiredFailure, string.Empty), }; if (text.Length == 0) { return Release1RoomWithNoNameHoldStatus.NoWork; } string receipt = $"room-with-no-name-hold-{text}-v1-a{attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(transition, attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Release1RoomWithNoNameHoldStatus.NoWork; } if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(attempt, "recording the hold miss was rejected: " + release1StoryRuntimeCommandResult.Message); return Release1RoomWithNoNameHoldStatus.Rejected; } RefreshOfferStatus(); return Release1RoomWithNoNameHoldStatus.Failed; } public Release1RoomWithNoNameDeliveryStatus ReconcileDelivery() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1RoomWithNoNameReviewStatus.Ready) { return Release1RoomWithNoNameDeliveryStatus.NoWork; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1RoomWithNoNameDeliveryStatus.NoWork; } Release1MissionRecord release1MissionRecord = Mission(state); int attempt = release1MissionRecord.Attempt; Release1RoomWithNoNameAssignment release1RoomWithNoNameAssignment = state.RoomWithNoNameAssignments.SingleOrDefault((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt == attempt); if ((object)release1RoomWithNoNameAssignment == null) { return Release1RoomWithNoNameDeliveryStatus.NoWork; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == ConsumptionEffectId(attempt)); if ((object)release1NativeEffectJournalEntry == null) { Release1RoomWithNoNameProgress? release1RoomWithNoNameProgress = Progress(attempt); if ((object)release1RoomWithNoNameProgress == null || !release1RoomWithNoNameProgress.HoldSatisfied) { return Release1RoomWithNoNameDeliveryStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord _, out Release1RoomWithNoNameAssignment _, out string authorization)) { return Release1RoomWithNoNameDeliveryStatus.NoWork; } return BeginDelivery(release1MissionRecord, release1RoomWithNoNameAssignment, authorization); } return ContinueDelivery(release1MissionRecord, release1RoomWithNoNameAssignment, release1NativeEffectJournalEntry); } private Release1RoomWithNoNameDeliveryStatus BeginDelivery(Release1MissionRecord mission, Release1RoomWithNoNameAssignment assignment, string authorization) { if (!TryReadSlots(assignment.HandoffDropGuid, out IReadOnlyList slots)) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } Release1SmallCourtesySlotSnapshot single; int num = Release1ConsignmentClassifier.CountMatches(slots, Fingerprint(assignment), out single); if (num == 0) { return Release1RoomWithNoNameDeliveryStatus.NoWork; } if (num > 1) { ReportHoldOnce(assignment.Attempt, "the hand off drop holds more than one matching consignment; Room With No Name will consume none of them."); return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } Release1SmallCourtesyCargoIdentity release1SmallCourtesyCargoIdentity; try { release1SmallCourtesyCargoIdentity = new Release1SmallCourtesyCargoIdentity(assignment.ProductId, assignment.PackagingId, single.SlotIndex, single.Quantity, single.Quantity - 1, single.MonetaryValue); } catch (ArgumentException) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } Release1NativeEffectJournalEntry prepared = new Release1NativeEffectJournalEntry(ConsumptionEffectId(assignment.Attempt), "release1.room-with-no-name", assignment.Attempt, "CargoTransfer", assignment.HandoffDropGuid, "oc-room-with-no-name", release1SmallCourtesyCargoIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, _story.State.Revision + 1, null, authorization, ExecutionBlocked: false, mission.Revision); if (!_story.TryPrepareNativeEffect(prepared).Accepted) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } Release1NativeEffectJournalEntry effect = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == prepared.EffectId); if (!ConsumeOneUnit(assignment, release1SmallCourtesyCargoIdentity, effect)) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } return FinishDelivery(Mission(_story.State), assignment, effect, release1SmallCourtesyCargoIdentity, authorization); } private Release1RoomWithNoNameDeliveryStatus ContinueDelivery(Release1MissionRecord mission, Release1RoomWithNoNameAssignment assignment, Release1NativeEffectJournalEntry effect) { string authorizedStoryCorrelationId = effect.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId) || !Release1SmallCourtesyCargoIdentity.TryParse(effect.AmountOrCargoIdentity, out Release1SmallCourtesyCargoIdentity identity) || (object)identity == null || effect.Attempt != mission.Attempt || !string.Equals(effect.EffectKind, "CargoTransfer", StringComparison.Ordinal) || !string.Equals(effect.SourceIdentity, assignment.HandoffDropGuid, StringComparison.Ordinal) || !string.Equals(effect.DestinationIdentity, "oc-room-with-no-name", StringComparison.Ordinal) || !string.Equals(identity.ProductId, assignment.ProductId, StringComparison.Ordinal) || !string.Equals(identity.PackageId, assignment.PackagingId, StringComparison.Ordinal)) { return BlockConsumption(effect); } if (effect.ExecutionBlocked) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } if (effect.Phase == Release1NativeEffectPhase.Prepared) { if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot)) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } bool flag = MatchesCargoSlot(slot, identity, identity.PreQuantity); if (MatchesCargoSlot(slot, identity, identity.PostQuantity)) { if (!MarkConsumptionApplied(effect)) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } } else { if (!flag) { return BlockConsumption(effect); } if (!ConsumeOneUnit(assignment, identity, effect)) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } } } return FinishDelivery(mission, assignment, effect, identity, authorizedStoryCorrelationId); } private Release1RoomWithNoNameDeliveryStatus FinishDelivery(Release1MissionRecord mission, Release1RoomWithNoNameAssignment assignment, Release1NativeEffectJournalEntry effect, Release1SmallCourtesyCargoIdentity identity, string authorization) { long revision = _story.State.Revision; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effect.EffectId); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } if (!TryCompleteAndPrepareReward(release1NativeEffectJournalEntry, assignment, identity)) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } Release1RoomWithNoNameDeliveryStatus release1RoomWithNoNameDeliveryStatus = ReconcileReward(assignment.Attempt, revision); if (release1RoomWithNoNameDeliveryStatus != Release1RoomWithNoNameDeliveryStatus.Paid && release1RoomWithNoNameDeliveryStatus != Release1RoomWithNoNameDeliveryStatus.Committed && release1RoomWithNoNameDeliveryStatus != Release1RoomWithNoNameDeliveryStatus.AwaitingAppliedSave) { return release1RoomWithNoNameDeliveryStatus; } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied && revision <= _story.LastPersistedRevision) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorization, revision); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(assignment.Attempt, "the consumption commit after a save was rejected: " + release1StoryRuntimeCommandResult.Message); } } return release1RoomWithNoNameDeliveryStatus switch { Release1RoomWithNoNameDeliveryStatus.Paid => Release1RoomWithNoNameDeliveryStatus.Paid, Release1RoomWithNoNameDeliveryStatus.Committed => Release1RoomWithNoNameDeliveryStatus.Committed, _ => Release1RoomWithNoNameDeliveryStatus.AwaitingAppliedSave, }; } private bool ConsumeOneUnit(Release1RoomWithNoNameAssignment assignment, Release1SmallCourtesyCargoIdentity identity, Release1NativeEffectJournalEntry effect) { if (!TryAcquireSlotLock(assignment.HandoffDropGuid, identity.SlotIndex)) { BlockConsumption(effect); return false; } try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeSlotQuantity(assignment.HandoffDropGuid, identity.SlotIndex, -1); } catch { BlockConsumption(effect); return false; } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { BlockConsumption(effect); return false; } if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot) || !MatchesCargoSlot(slot, identity, identity.PostQuantity)) { BlockConsumption(effect); return false; } return MarkConsumptionApplied(effect); } finally { ReleaseSlotLock(); } } private bool MarkConsumptionApplied(Release1NativeEffectJournalEntry effect) { return _story.TryMarkNativeEffectApplied(effect.EffectId, $"room-with-no-name-cargo-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted; } private bool TryCompleteAndPrepareReward(Release1NativeEffectJournalEntry consumption, Release1RoomWithNoNameAssignment assignment, Release1SmallCourtesyCargoIdentity identity) { Release1StoryState state = _story.State; if ((object)state == null) { return false; } Release1MissionRecord mission = Mission(state); Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry effect) => effect.EffectId == RewardEffectId(mission.Attempt)); if (mission.State == Release1MissionState.Satisfied) { return (object)release1NativeEffectJournalEntry != null; } Release1MissionState state2 = mission.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive || state2 == Release1MissionState.RecoveryActive) ? true : false); if (!flag || consumption.Phase != Release1NativeEffectPhase.Applied || consumption.ExecutionBlocked || consumption.Attempt != mission.Attempt || (object)release1NativeEffectJournalEntry != null || !TryReadCashBalance(out var balance)) { return false; } float? num = Release1RewardMath.QuoteWholeDollars(identity.DepositedMonetaryValue, (decimal)assignment.RewardMultiplier); if (num.HasValue) { float valueOrDefault = num.GetValueOrDefault(); Release1SmallCourtesyRewardIdentity release1SmallCourtesyRewardIdentity; try { release1SmallCourtesyRewardIdentity = new Release1SmallCourtesyRewardIdentity(balance, valueOrDefault, balance + valueOrDefault); } catch (ArgumentException) { return false; } if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return false; } string receiptId = $"room-with-no-name-complete-v1-a{mission.Attempt}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.room-with-no-name", mission.Attempt, Release1TransitionKind.MissionCompleted, receiptId).Value; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = new Release1NativeEffectJournalEntry(RewardEffectId(mission.Attempt), "release1.room-with-no-name", mission.Attempt, "Reward", "oc-room-with-no-name", "player-cash", release1SmallCourtesyRewardIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, state.Revision + 1, null, value, ExecutionBlocked: false, mission.Revision); Guid sessionEpoch = snapshot.SessionEpoch; long loadEpoch = snapshot.LoadEpoch; string playerId = snapshot.PlayerId; int attempt = mission.Attempt; Release1CompletionTiming? completionTiming = ((mission.State != Release1MissionState.Active) ? Release1CompletionTiming.Late : Release1CompletionTiming.OnTime); string rewardAuthorizationReceiptId = $"room-with-no-name-reward-auth-v1-a{mission.Attempt}"; Release1NativeEffectJournalEntry preparedNativeEffect = release1NativeEffectJournalEntry2; Release1StoryCommand command = new Release1StoryCommand(sessionEpoch, loadEpoch, playerId, "release1.room-with-no-name", attempt, Release1TransitionKind.MissionCompleted, receiptId, value, null, null, null, completionTiming, rewardAuthorizationReceiptId, null, null, preparedNativeEffect); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecute(command); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(mission.Attempt, "completing the mission after the unit was consumed was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } return false; } private Release1RoomWithNoNameDeliveryStatus ReconcileReward(int attempt, long capturedRevision) { Release1StoryState? state = _story.State; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state?.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == RewardEffectId(attempt)); if ((object)state == null || (object)release1NativeEffectJournalEntry == null) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } if (!Release1SmallCourtesyRewardIdentity.TryParse(release1NativeEffectJournalEntry.AmountOrCargoIdentity, out Release1SmallCourtesyRewardIdentity identity) || (object)identity == null || !string.Equals(release1NativeEffectJournalEntry.EffectKind, "Reward", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.SourceIdentity, "oc-room-with-no-name", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.DestinationIdentity, "player-cash", StringComparison.Ordinal)) { return BlockReward(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.ExecutionBlocked) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } if (!TryReadCashBalance(out var balance)) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } bool flag = WithinCashTolerance(balance, identity.BaselineCash, identity.VerificationTolerance); bool flag2 = WithinCashTolerance(balance, identity.ExpectedCash, identity.VerificationTolerance); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { if (flag2) { return MarkRewardApplied(release1NativeEffectJournalEntry); } if (!flag) { return BlockReward(release1NativeEffectJournalEntry); } Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeCashBalance(identity.WholeDollarAmount); } catch { return BlockReward(release1NativeEffectJournalEntry); } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return BlockReward(release1NativeEffectJournalEntry); } if (!TryReadCashBalance(out var balance2) || !WithinCashTolerance(balance2, identity.ExpectedCash, identity.VerificationTolerance)) { return BlockReward(release1NativeEffectJournalEntry); } return MarkRewardApplied(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied) { if (!flag2) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } if (capturedRevision > _story.LastPersistedRevision) { return Release1RoomWithNoNameDeliveryStatus.AwaitingAppliedSave; } string authorizedStoryCorrelationId = release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId)) { return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } if (!_story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorizedStoryCorrelationId, capturedRevision).Accepted) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } return Release1RoomWithNoNameDeliveryStatus.Committed; } return Release1RoomWithNoNameDeliveryStatus.Committed; } private Release1RoomWithNoNameDeliveryStatus MarkRewardApplied(Release1NativeEffectJournalEntry effect) { if (!_story.TryMarkNativeEffectApplied(effect.EffectId, $"room-with-no-name-reward-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted) { return Release1RoomWithNoNameDeliveryStatus.Rejected; } return Release1RoomWithNoNameDeliveryStatus.Paid; } private Release1RoomWithNoNameDeliveryStatus BlockConsumption(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } ReleaseSlotLock(); return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } private Release1RoomWithNoNameDeliveryStatus BlockReward(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } return Release1RoomWithNoNameDeliveryStatus.Ambiguous; } private bool TryReadCashBalance(out float balance) { balance = 0f; try { return _world.TryReadCashBalance(out balance) == Release1SmallCourtesyWorldReadStatus.Ready && float.IsFinite(balance) && balance >= 0f; } catch { return false; } } private bool TryAcquireSlotLock(string deadDropGuid, int slotIndex) { if (_lockedDropGuid != null) { if (string.Equals(_lockedDropGuid, deadDropGuid, StringComparison.Ordinal)) { return _lockedSlotIndex == slotIndex; } return false; } _lockedDropGuid = deadDropGuid; _lockedSlotIndex = slotIndex; try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = _world.TrySetSlotLocked(deadDropGuid, slotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { if (release1SmallCourtesyWorldMutationStatus == Release1SmallCourtesyWorldMutationStatus.Ambiguous) { ReleaseSlotLock(); } else { _lockedDropGuid = null; _lockedSlotIndex = -1; } return false; } return true; } catch { ReleaseSlotLock(); return false; } } private static bool WithinCashTolerance(float actual, float expected, float tolerance) { if (float.IsFinite(actual)) { return MathF.Abs(actual - expected) <= tolerance; } return false; } private static bool MatchesCargoSlot(Release1SmallCourtesySlotSnapshot slot, Release1SmallCourtesyCargoIdentity identity, int expectedQuantity) { if (slot.SlotIndex != identity.SlotIndex || slot.Quantity != expectedQuantity) { return false; } if (expectedQuantity == 0) { return true; } if (slot.IsPackaged && string.Equals(slot.ProductId, identity.ProductId, StringComparison.Ordinal) && string.Equals(slot.PackagingId, identity.PackageId, StringComparison.Ordinal)) { return slot.MonetaryValue == identity.DepositedMonetaryValue; } return false; } private bool TryReadSlot(string deadDropGuid, int slotIndex, out Release1SmallCourtesySlotSnapshot slot) { slot = null; if (!TryReadSlots(deadDropGuid, out IReadOnlyList slots)) { return false; } slot = slots.SingleOrDefault((Release1SmallCourtesySlotSnapshot candidate) => candidate.SlotIndex == slotIndex); return (object)slot != null; } private Release1RoomWithNoNameStageStatus Converge() { Release1RoomWithNoNameStageStatus result = ReconcileStaging(); ReconcileHold(); ReconcileDelivery(); RefreshDropBinding(); TryQueueArthur(); return result; } private void TryQueueArthur() { if (_phone == null || _disposed || !_loadActive || _saving) { return; } Release1StoryState state = _story.State; if ((object)state == null || !_story.TryGetActiveContext(out var snapshot, out var _)) { return; } Release1MissionRecord release1MissionRecord = Mission(state); if (release1MissionRecord.State != Release1MissionState.MakeGoodOffered || release1MissionRecord.LastOutcome != Release1MissionOutcome.RequiredFailure) { return; } try { Release1PhoneCallRequest request = Release1PhoneCallRequest.Create(snapshot, "release1.room-with-no-name", release1MissionRecord.Attempt, Release1PhoneCallCorrelation.Create(snapshot.PlayerId, "release1.room-with-no-name", release1MissionRecord.Attempt, Release1PhoneCallRole.Arthur, "rn-warning-v1"), Release1PhoneCallRole.Arthur, "Arthur Selby", ArthurWarningStages, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.room-with-no-name", release1MissionRecord.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-rn-accepted-v1").Value); _phone.TryQueue(request); } catch (Exception ex) { _log("Room With No Name Arthur request was not published: " + ex.GetType().Name); } } private void RefreshDropBinding() { if (!_loadActive || _disposed || !TryGetActiveStage(out Release1MissionRecord _, out Release1RoomWithNoNameAssignment assignment, out string _)) { DisposeDropSubscription(); return; } Release1RoomWithNoNameProgress release1RoomWithNoNameProgress = Progress(assignment.Attempt); string text = (((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress.HoldSatisfied) ? assignment.HandoffDropGuid : (((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress.Custody) ? null : assignment.SourceDropGuid)); if (text == null) { DisposeDropSubscription(); } else { if (_dropSubscription != null && string.Equals(_boundDropGuid, text, StringComparison.Ordinal)) { return; } DisposeDropSubscription(); try { if (_world.TrySubscribeDeadDropClosed(text, OnDropClosed, out IRelease1SmallCourtesyDropSubscription subscription) != Release1SmallCourtesyWorldReadStatus.Ready || subscription == null || !string.Equals(subscription.DeadDropGuid, text, StringComparison.Ordinal)) { subscription?.Dispose(); return; } _dropSubscription = subscription; _boundDropGuid = text; } catch { DisposeDropSubscription(); } } } private void OnDropClosed(string deadDropGuid) { TryHandleDropClosed(deadDropGuid); } private void DisposeDropSubscription() { try { _dropSubscription?.Dispose(); } catch { } _dropSubscription = null; _boundDropGuid = null; } internal static Release1RoomWithNoNameAssignmentMode? ExpectedAssignmentMode(Release1MissionState missionState) { switch (missionState) { case Release1MissionState.Accepted: case Release1MissionState.Active: return Release1RoomWithNoNameAssignmentMode.Primary; case Release1MissionState.MakeGoodOffered: case Release1MissionState.MakeGoodActive: return Release1RoomWithNoNameAssignmentMode.MakeGood; case Release1MissionState.RecoveryAvailable: case Release1MissionState.RecoveryActive: return Release1RoomWithNoNameAssignmentMode.Recovery; default: return null; } } private bool TryGetActiveStage(out Release1MissionRecord mission, out Release1RoomWithNoNameAssignment assignment, out string authorization) { mission = null; assignment = null; authorization = string.Empty; Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return false; } mission = Mission(state); Release1RoomWithNoNameAssignmentMode? expectedMode = ExpectedAssignmentMode(mission.State); if (!expectedMode.HasValue) { return false; } int missionAttempt = mission.Attempt; assignment = state.RoomWithNoNameAssignments.SingleOrDefault((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt == missionAttempt && candidate.Mode == expectedMode.Value); if ((object)assignment == null) { return false; } authorization = ((expectedMode == Release1RoomWithNoNameAssignmentMode.Primary) ? (mission.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) && correlation.TransitionKind == Release1TransitionKind.MissionActivated) ?? string.Empty) : assignment.AuthorizationCorrelationId); return !string.IsNullOrEmpty(authorization); } private Release1RoomWithNoNameProgress? Progress(int attempt) { return _story.State?.RoomWithNoNameProgress.SingleOrDefault((Release1RoomWithNoNameProgress progress) => progress.Attempt == attempt); } private bool SetProgress(int attempt, bool? staged = null, bool? custody = null, bool? stowed = null, bool? holdSatisfied = null, double? stowedAtGameMinutes = null, string? holdingClosetGuid = null, double? missingSincePassGameMinutes = null, bool clearMissingSince = false) { Release1RoomWithNoNameProgress release1RoomWithNoNameProgress = Progress(attempt) ?? Release1RoomWithNoNameProgress.Fresh(attempt); Release1RoomWithNoNameProgress next = release1RoomWithNoNameProgress with { Staged = (staged ?? release1RoomWithNoNameProgress.Staged), Custody = (custody ?? release1RoomWithNoNameProgress.Custody), Stowed = (stowed ?? release1RoomWithNoNameProgress.Stowed), HoldSatisfied = (holdSatisfied ?? release1RoomWithNoNameProgress.HoldSatisfied), StowedAtGameMinutes = (stowedAtGameMinutes ?? release1RoomWithNoNameProgress.StowedAtGameMinutes), HoldingClosetGuid = (holdingClosetGuid ?? release1RoomWithNoNameProgress.HoldingClosetGuid), MissingSincePassGameMinutes = (clearMissingSince ? ((double?)null) : (missingSincePassGameMinutes ?? release1RoomWithNoNameProgress.MissingSincePassGameMinutes)) }; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetRoomWithNoNameProgress(next); if (!release1StoryRuntimeCommandResult.Accepted && release1StoryRuntimeCommandResult.Status != Release1StoryCommandStatus.NoOp) { ReportHoldOnce(attempt, "recording progress was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } private bool HasConsumptionEffect(int attempt) { return _story.State?.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.EffectId == ConsumptionEffectId(attempt)) ?? false; } private bool TryReadSlots(string deadDropGuid, out IReadOnlyList slots) { slots = Array.Empty(); try { if (_world.TryReadDeadDropSlots(deadDropGuid, out IReadOnlyList slots2) != Release1SmallCourtesyWorldReadStatus.Ready || slots2 == null) { return false; } Release1SmallCourtesySlotSnapshot[] array = slots2.ToArray(); Release1SmallCourtesySlotSnapshot[] array2 = array; for (int i = 0; i < array2.Length; i++) { array2[i].Validate(); } if (array.Select((Release1SmallCourtesySlotSnapshot slot) => slot.SlotIndex).Distinct().Count() != array.Length) { return false; } slots = array; return true; } catch { return false; } } private void ReleaseSlotLock() { if (_lockedDropGuid == null || _lockedSlotIndex < 0) { return; } try { if (_world.TrySetSlotLocked(_lockedDropGuid, _lockedSlotIndex, locked: false) == Release1SmallCourtesyWorldMutationStatus.Succeeded) { _lockedDropGuid = null; _lockedSlotIndex = -1; } } catch { } } private void ReportHoldOnce(int attempt, string message) { if (_reportedHolds.Add($"a{attempt}:{message}")) { _log($"Room With No Name attempt {attempt} is holding: {message}"); } } private static Release1ConsignmentFingerprint Fingerprint(Release1RoomWithNoNameAssignment assignment) { return new Release1ConsignmentFingerprint(assignment.ProductId, assignment.PackagingId, assignment.PackageQuantity); } private static string ConsumptionEffectId(int attempt) { return $"room-with-no-name-cargo-v1-a{attempt}"; } private static string RewardEffectId(int attempt) { return $"room-with-no-name-reward-v1-a{attempt}"; } } public enum Release1RoomWithNoNameCardStage { Hidden, Offer, Review, InsufficientDrops, PersistenceDeferred, DecisionRejected, AwaitingActivation, Active, InCustody, Stowed, Released, Ambiguous, Completed } public sealed record Release1RoomWithNoNameViewModel(bool Visible, Release1RoomWithNoNameCardStage Stage, string Title, string Body, bool CanReview, bool CanAccept, bool CanDefer); public static class Release1RoomWithNoNamePresentation { public const string Title = "A Room With No Name"; public const string OfferText = "I have shit that needs to sit somewhere quiet for a day. Don't even think about selling it, or you will hear from Arthur."; public const string RefusalText = "I need two clear drops before I can set this up. Try me again when the town has room."; public const string QuestionText = "Are you taking it?"; public const string PaymentText = "I pay 150 percent of the value the moment you hand it over. Fuck it up and you get nothing."; public static Release1RoomWithNoNameViewModel Build(Release1StoryState? state, Release1RoomWithNoNameOfferStatus offerStatus, Release1RoomWithNoNameQuote? reviewedQuote, Release1RoomWithNoNameProgress? progress, string? feedback = null, Release1RoomWithNoNameCardStage? feedbackStage = null) { if (!string.IsNullOrWhiteSpace(feedback)) { return Visible(feedbackStage ?? Release1RoomWithNoNameCardStage.PersistenceDeferred, Copy(feedback)); } if (offerStatus == Release1RoomWithNoNameOfferStatus.InsufficientEmptyDeadDrops) { return new Release1RoomWithNoNameViewModel(Visible: true, Release1RoomWithNoNameCardStage.InsufficientDrops, "A Room With No Name", Copy("I need two clear drops before I can set this up. Try me again when the town has room."), CanReview: true, CanAccept: false, CanDefer: false); } if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Hidden(); } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; Release1RoomWithNoNameAssignmentMode? expectedMode = Release1RoomWithNoNameMissionService.ExpectedAssignmentMode(mission.State); Release1RoomWithNoNameAssignment release1RoomWithNoNameAssignment = state.RoomWithNoNameAssignments.SingleOrDefault((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt == mission.Attempt && (!expectedMode.HasValue || candidate.Mode == expectedMode.Value)); if ((object)release1RoomWithNoNameAssignment == null) { if ((object)reviewedQuote != null) { return Terms(reviewedQuote.Assignment, reviewedQuote.DeadlineDurationHours); } if (offerStatus == Release1RoomWithNoNameOfferStatus.Available) { return new Release1RoomWithNoNameViewModel(Visible: true, Release1RoomWithNoNameCardStage.Offer, "A Room With No Name", Copy("I have shit that needs to sit somewhere quiet for a day. Don't even think about selling it, or you will hear from Arthur."), CanReview: true, CanAccept: false, CanDefer: false); } return Hidden(); } if (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.room-with-no-name" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked)) { return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.Ambiguous, "Stopped on conflicting evidence. It will not retry."); } if (mission.State == Release1MissionState.Satisfied) { return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.Completed, "Complete. Delivered and paid."); } if (mission.State == Release1MissionState.Accepted) { return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.AwaitingActivation, "Collect it from the pick up."); } if ((object)progress != null && progress.HoldSatisfied) { return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.Released, "The hold is done. Take it to the drop off."); } if ((object)progress != null && progress.Stowed) { return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.Stowed, "It stays in the room. One day."); } if ((object)progress != null && progress.Custody) { return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.InCustody, "Take it to the room and leave it there. No fucking around on the way there."); } return Active(release1RoomWithNoNameAssignment, mission, Release1RoomWithNoNameCardStage.Active, "Collect it from the pick up."); } private static Release1RoomWithNoNameViewModel Terms(Release1RoomWithNoNameAssignment assignment, double? deadlineHours) { return new Release1RoomWithNoNameViewModel(Visible: true, Release1RoomWithNoNameCardStage.Review, "A Room With No Name", Copy(Release1PlayerCopy.JoinLines(Release1PlayerCopy.AttemptHeader(assignment.Mode, assignment.Attempt), "1 " + assignment.PackagingName + " of " + assignment.ProductName, $"Pick up: {assignment.SourceDropName}. Drop off: {assignment.HandoffDropName}.", "Leave it alone in the storage room at HQ for one day.", "Deadline: " + Release1PlayerCopy.Deadline(deadlineHours), "I pay 150 percent of the value the moment you hand it over. Fuck it up and you get nothing.", "Are you taking it?")), CanReview: false, CanAccept: true, CanDefer: true); } private static Release1RoomWithNoNameViewModel Active(Release1RoomWithNoNameAssignment assignment, Release1MissionRecord mission, Release1RoomWithNoNameCardStage stage, string status) { return new Release1RoomWithNoNameViewModel(Visible: true, stage, "A Room With No Name", Copy(string.Join("\n", "Status: " + status, "1 " + assignment.PackagingName + " of " + assignment.ProductName, "Room: the storage room at HQ", "Drop off: " + assignment.HandoffDropName, "Deadline: " + Release1PlayerCopy.Deadline(DeadlineDuration(mission)), "I pay 150 percent of the value the moment you hand it over. Fuck it up and you get nothing.")), CanReview: false, CanAccept: false, CanDefer: false); } private static double? DeadlineDuration(Release1MissionRecord mission) { if (mission.DeadlineGameTimeHours.HasValue && mission.AcceptedGameTimeHours.HasValue) { return mission.DeadlineGameTimeHours.Value - mission.AcceptedGameTimeHours.Value; } return null; } private static Release1RoomWithNoNameViewModel Visible(Release1RoomWithNoNameCardStage stage, string body) { return new Release1RoomWithNoNameViewModel(Visible: true, stage, "A Room With No Name", body, CanReview: false, CanAccept: false, CanDefer: false); } private static Release1RoomWithNoNameViewModel Hidden() { return new Release1RoomWithNoNameViewModel(Visible: false, Release1RoomWithNoNameCardStage.Hidden, "A Room With No Name", string.Empty, CanReview: false, CanAccept: false, CanDefer: false); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1RoomWithNoNamePresenter : IDisposable { private readonly Release1RoomWithNoNameMissionService _service; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private string? _feedback; private Release1RoomWithNoNameCardStage? _feedbackStage; private bool _disposed; public Release1RoomWithNoNameViewModel View { get { if (!_disposed) { return Release1RoomWithNoNamePresentation.Build(_story.State, _service.OfferStatus, _service.ReviewedQuote, CurrentProgress(), _feedback, _feedbackStage); } return Release1RoomWithNoNamePresentation.Build(null, Release1RoomWithNoNameOfferStatus.Disposed, null, null); } } public Release1RoomWithNoNamePresenter(Release1RoomWithNoNameMissionService service, Release1StoryRuntimeService story, Action? log = null) { _service = service ?? throw new ArgumentNullException("service"); _story = story ?? throw new ArgumentNullException("story"); _log = log; } public Release1RoomWithNoNameReviewResult TryReview() { Release1RoomWithNoNameReviewResult release1RoomWithNoNameReviewResult = _service.TryReview(); _feedback = ((release1RoomWithNoNameReviewResult.Status == Release1RoomWithNoNameReviewStatus.Ready) ? null : release1RoomWithNoNameReviewResult.Message); _feedbackStage = ((_feedback == null) ? ((Release1RoomWithNoNameCardStage?)null) : new Release1RoomWithNoNameCardStage?((release1RoomWithNoNameReviewResult.Status == Release1RoomWithNoNameReviewStatus.InsufficientEmptyDeadDrops) ? Release1RoomWithNoNameCardStage.InsufficientDrops : Release1RoomWithNoNameCardStage.DecisionRejected)); if (_feedback != null) { _log?.Invoke($"Room With No Name review failed: {release1RoomWithNoNameReviewResult.Status}, {release1RoomWithNoNameReviewResult.Message}"); } return release1RoomWithNoNameReviewResult; } public Release1RoomWithNoNameDecisionResult TryAccept() { return Handle(_service.TryAccept()); } public Release1RoomWithNoNameDecisionResult TryDefer() { return Handle(_service.TryDefer()); } public void ClearFeedback() { _feedback = null; _feedbackStage = null; } public void Dispose() { _feedback = null; _feedbackStage = null; _disposed = true; } private Release1RoomWithNoNameProgress? CurrentProgress() { Release1StoryState state = _story.State; if ((object)state == null) { return null; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; return state.RoomWithNoNameProgress.SingleOrDefault((Release1RoomWithNoNameProgress progress) => progress.Attempt == mission.Attempt); } private Release1RoomWithNoNameDecisionResult Handle(Release1RoomWithNoNameDecisionResult result) { Release1RoomWithNoNameDecisionStatus status = result.Status; if ((uint)status <= 2u) { _feedback = null; _feedbackStage = null; } else if (result.Status == Release1RoomWithNoNameDecisionStatus.InsufficientEmptyDeadDrops) { _feedback = null; _feedbackStage = null; } else { _feedback = result.Message; _feedbackStage = ((result.Status == Release1RoomWithNoNameDecisionStatus.PersistenceDeferred) ? Release1RoomWithNoNameCardStage.PersistenceDeferred : Release1RoomWithNoNameCardStage.DecisionRejected); _log?.Invoke($"Room With No Name decision failed: {result.Status}, {result.Message}"); } return result; } } public sealed class Release1RoomWithNoNameQuest : Quest { public const string DisplayTitle = "A Room With No Name"; private readonly QuestEntry[] _entries; protected override string Title => Release1PlayerCopy.Normalize("A Room With No Name"); protected override string Description => Release1PlayerCopy.Normalize("A Room With No Name"); public Release1RoomWithNoNameQuest() { string text = Release1PlayerCopy.Normalize("A Room With No Name"); _entries = (QuestEntry[])(object)new QuestEntry[3] { ((Quest)this).AddEntry(text, (Vector3?)null), ((Quest)this).AddEntry(text, (Vector3?)null), ((Quest)this).AddEntry(text, (Vector3?)null) }; } public void Project(Release1DesiredQuest quest) { ArgumentNullException.ThrowIfNull(quest, "quest"); if (quest.Entries.Count != _entries.Length) { throw new InvalidOperationException($"Release 1 Room With No Name quest desired entry count ({quest.Entries.Count}) does not match the quest's native entry count ({_entries.Length})."); } for (int i = 0; i < _entries.Length; i++) { ApplyEntry(_entries[i], quest.Entries[i]); } } public IReadOnlyList ReadEntryStates() { return _entries.Select((QuestEntry entry) => MapState(entry.State)).ToArray(); } private static void ApplyEntry(QuestEntry entry, Release1DesiredEntry desired) { //IL_0035: 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) entry.Title = Release1PlayerCopy.Normalize(desired.Text); Release1WorldPoint marker = desired.Marker; entry.POIPosition = (Vector3)(((object)marker != null) ? new Vector3(marker.X, marker.Y, marker.Z) : Vector3.zero); switch (Release1NativePresentationSupport.ClassifyEntryAction(desired.State)) { case Release1QuestEntryProjectionAction.Begin: entry.Begin(); break; case Release1QuestEntryProjectionAction.Complete: entry.Complete(); break; default: entry.SetState((QuestState)0); break; } } private static Release1DesiredEntryState MapState(QuestState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 1) { if ((int)state == 2) { return Release1DesiredEntryState.Complete; } return Release1DesiredEntryState.Inactive; } return Release1DesiredEntryState.Active; } } public sealed class Release1ShortNoticeComposition : IDisposable { private bool _disposed; public Release1ShortNoticeMissionService Service { get; } public Release1ShortNoticePresenter Presenter { get; } public Release1ShortNoticeComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService phone, Action? log = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(world, "world"); ArgumentNullException.ThrowIfNull(phone, "phone"); Service = new Release1ShortNoticeMissionService(story, world, phone, log); Presenter = new Release1ShortNoticePresenter(Service, story, log); } public void OnLoadComplete() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnLoadComplete(); } } public void OnPreLoad() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnPreLoad(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); } } public void Update() { if (!_disposed) { Service.Update(); } } public void Dispose() { if (!_disposed) { _disposed = true; Presenter.Dispose(); Service.Dispose(); } } } public enum Release1ShortNoticeOfferStatus { Inactive, Available, Ineligible, Pending, Saving, NoDiscoveredProduct, NoEmptyDeadDrop, Unavailable, Dismissed, Disposed } public enum Release1ShortNoticeReviewStatus { Ready, Inactive, Ineligible, Pending, Saving, NoDiscoveredProduct, NoEmptyDeadDrop, Unavailable, Dismissed, Disposed } public enum Release1ShortNoticeDecisionStatus { Accepted, Deferred, Dismissed, ReviewRequired, QuoteChanged, NoEmptyDeadDrop, PersistenceDeferred, Rejected, Inactive, Disposed } public enum Release1ShortNoticeObservationStatus { NoWork, Shortfall, Spread, Ready, Unavailable, Rejected } public enum Release1ShortNoticeDepositStatus { NoWork, Paid, AwaitingAppliedSave, Committed, Ambiguous, Rejected } public sealed record Release1ShortNoticeQuote(Release1ShortNoticeAssignment Assignment, double? DeadlineDurationHours) { public void Validate() { Assignment.Validate(); if (Assignment.Mode == Release1ShortNoticeAssignmentMode.Recovery) { if (DeadlineDurationHours.HasValue) { throw new ArgumentException("Recovery quotes cannot have a deadline.", "DeadlineDurationHours"); } } else if (DeadlineDurationHours.GetValueOrDefault() != 12.0) { throw new ArgumentException("Timed Short Notice stages require a 12-hour duration.", "DeadlineDurationHours"); } } } public sealed record Release1ShortNoticeReviewResult(Release1ShortNoticeReviewStatus Status, Release1ShortNoticeQuote? Quote, string Message); public sealed record Release1ShortNoticeDecisionResult(Release1ShortNoticeDecisionStatus Status, string Message); public sealed class Release1ShortNoticeMissionService : IDisposable { internal readonly record struct Option(T Value) { public static implicit operator Option(T value) { return new Option(value); } } private const string TermsVersion = "short-notice-v1"; private const double TimedStageDurationHours = 12.0; private const string EffectScope = "oc-short-notice"; private const string RewardDestination = "player-cash"; private const string ArthurCallerLabel = "Arthur Selby"; private static readonly string[] ArthurWarningStages = new string[3] { "The order did not land and the window is closed. If you are trying to pull a fast one, I will fuck you up.", "Nell has arranged one more run. Two bricks, twelve hours from the moment you accept it.", "Do not make her ask twice. You are pissing me off." }; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1PhoneCallService? _phone; private readonly Action _log; private bool _loadActive; private bool _saving; private bool _dismissed; private bool _disposed; private Release1ShortNoticeQuote? _reviewedQuote; private readonly HashSet _reportedHolds = new HashSet(StringComparer.Ordinal); private IRelease1SmallCourtesyDropSubscription? _dropSubscription; private string? _boundDropGuid; private string? _lockedDropGuid; private int _lockedSlotIndex = -1; public Release1ShortNoticeOfferStatus OfferStatus { get; private set; } public Release1ShortNoticeQuote? ReviewedQuote => _reviewedQuote; public Release1ShortNoticeMissionService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService? phone = null, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _phone = phone; _log = log ?? ((Action)delegate { }); } public void OnLoadComplete() { if (!_disposed && !_loadActive) { _loadActive = true; _saving = false; _dismissed = false; _reviewedQuote = null; _reportedHolds.Clear(); RefreshOfferStatus(); Converge(); } } public void OnPreLoad() { if (!_disposed) { ReleaseSlotLock(); DisposeDropSubscription(); _loadActive = false; _saving = false; _dismissed = false; _reviewedQuote = null; _reportedHolds.Clear(); OfferStatus = Release1ShortNoticeOfferStatus.Inactive; } } public void OnSaveStart() { if (!_disposed && _loadActive) { _saving = true; OfferStatus = Release1ShortNoticeOfferStatus.Saving; } } public void OnSaveComplete() { if (!_disposed && _loadActive) { _saving = false; RefreshOfferStatus(); Converge(); } } public void Update() { if (_disposed || !_loadActive || _saving) { return; } Release1StoryHostContextSnapshot context; Release1ShortNoticeReviewStatus release1ShortNoticeReviewStatus = ReadMatchingContext(out context); if (release1ShortNoticeReviewStatus != Release1ShortNoticeReviewStatus.Ready) { OfferStatus = Map(release1ShortNoticeReviewStatus); return; } Release1StoryState state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Accepted) { Release1MissionRecord release1MissionRecord = Mission(state); Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive) ? true : false); if (flag && release1MissionRecord.DeadlineGameTimeHours.HasValue && TryReadGameHours(out var gameHours) && gameHours >= release1MissionRecord.DeadlineGameTimeHours.Value && !HasConsumptionEffect(release1MissionRecord.Attempt)) { Release1TransitionKind transition = ((release1MissionRecord.State == Release1MissionState.Active) ? Release1TransitionKind.RequiredFailure : Release1TransitionKind.MakeGoodFailed); string receipt = $"short-notice-deadline-{DeadlineToken(release1MissionRecord.State)}-v1-a{release1MissionRecord.Attempt}"; _story.TryExecuteDurably(CreateCommand(transition, release1MissionRecord.Attempt, receipt, null, null, null)); RefreshOfferStatus(); } Converge(); } } public Release1ShortNoticeReviewResult TryReview() { if (_disposed) { return Review(Release1ShortNoticeReviewStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Review(Release1ShortNoticeReviewStatus.Inactive, "No load epoch is active."); } if (_saving) { return Review(Release1ShortNoticeReviewStatus.Saving, "Mission review is deferred during saving."); } if (_dismissed) { return Review(Release1ShortNoticeReviewStatus.Dismissed, "Offer was dismissed for this load."); } Release1ShortNoticeQuote quote; Release1ShortNoticeReviewStatus release1ShortNoticeReviewStatus = TryBuildQuote(out quote); if (release1ShortNoticeReviewStatus != Release1ShortNoticeReviewStatus.Ready) { _reviewedQuote = null; OfferStatus = Map(release1ShortNoticeReviewStatus); return Review(release1ShortNoticeReviewStatus, Message(release1ShortNoticeReviewStatus)); } _reviewedQuote = quote; OfferStatus = Release1ShortNoticeOfferStatus.Available; return new Release1ShortNoticeReviewResult(Release1ShortNoticeReviewStatus.Ready, quote, "Short Notice terms are ready for review."); } public Release1ShortNoticeDecisionResult TryAccept() { if (_disposed) { return Decision(Release1ShortNoticeDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _dismissed) { return Decision(Release1ShortNoticeDecisionStatus.Inactive, "No active offer is available."); } if (_saving) { return Decision(Release1ShortNoticeDecisionStatus.PersistenceDeferred, "Acceptance is deferred during saving."); } if ((object)_reviewedQuote == null) { return Decision(Release1ShortNoticeDecisionStatus.ReviewRequired, "Review the current terms before accepting."); } Release1ShortNoticeQuote quote; Release1ShortNoticeReviewStatus release1ShortNoticeReviewStatus = TryBuildQuote(out quote); switch (release1ShortNoticeReviewStatus) { case Release1ShortNoticeReviewStatus.NoEmptyDeadDrop: _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1ShortNoticeDecisionStatus.NoEmptyDeadDrop, Message(release1ShortNoticeReviewStatus)); case Release1ShortNoticeReviewStatus.Ready: if (!(quote != _reviewedQuote)) { if (!TryReadGameHours(out var gameHours)) { return Decision(Release1ShortNoticeDecisionStatus.Rejected, "Canonical game time was unavailable."); } Release1ShortNoticeAssignment assignment = quote.Assignment; Release1StoryCommand command = CreateCommand(assignment.Mode switch { Release1ShortNoticeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1ShortNoticeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1ShortNoticeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }, assignment.Attempt, AcceptanceReceipt(assignment.Mode, assignment.Attempt), (assignment.Mode == Release1ShortNoticeAssignmentMode.Primary) ? "short-notice-v1" : null, gameHours, (assignment.Mode == Release1ShortNoticeAssignmentMode.Recovery) ? ((double?)null) : new double?(gameHours + 12.0)); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteShortNoticeAcceptanceDurably(command, assignment); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1ShortNoticeDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1ShortNoticeDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } _reviewedQuote = null; RefreshOfferStatus(); Converge(); return Decision(Release1ShortNoticeDecisionStatus.Accepted, "Short Notice stage was accepted."); } goto default; default: _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1ShortNoticeDecisionStatus.QuoteChanged, "The available product, package, or drop changed; review again."); } } public Release1ShortNoticeDecisionResult TryDefer() { if (_disposed) { return Decision(Release1ShortNoticeDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _saving || ReadMatchingContext(out var _) != Release1ShortNoticeReviewStatus.Ready) { return Decision(Release1ShortNoticeDecisionStatus.Inactive, "No active offer is available."); } Release1StoryState state = _story.State; Release1MissionRecord release1MissionRecord = (((object)state == null) ? null : Mission(state)); if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted || (object)release1MissionRecord == null || release1MissionRecord.State != Release1MissionState.Offered) { return TryDismiss(); } string receipt = $"short-notice-defer-v1-a{release1MissionRecord.Attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(Release1TransitionKind.MissionDeferred, release1MissionRecord.Attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1ShortNoticeDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1ShortNoticeDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } DismissForLoad(); return Decision(Release1ShortNoticeDecisionStatus.Deferred, "Short Notice was deferred until a later load."); } public Release1ShortNoticeDecisionResult TryDismiss() { if (_disposed) { return Decision(Release1ShortNoticeDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Decision(Release1ShortNoticeDecisionStatus.Inactive, "No load epoch is active."); } DismissForLoad(); return Decision(Release1ShortNoticeDecisionStatus.Dismissed, "Short Notice was dismissed for this load."); } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; OfferStatus = Release1ShortNoticeOfferStatus.Disposed; } } private Release1ShortNoticeReviewStatus TryBuildQuote(out Release1ShortNoticeQuote? quote) { try { return TryBuildQuoteCore(out quote); } catch { quote = null; return Release1ShortNoticeReviewStatus.Unavailable; } } private Release1ShortNoticeReviewStatus TryBuildQuoteCore(out Release1ShortNoticeQuote? quote) { quote = null; Release1StoryHostContextSnapshot context; Release1ShortNoticeReviewStatus release1ShortNoticeReviewStatus = ReadMatchingContext(out context); if (release1ShortNoticeReviewStatus != Release1ShortNoticeReviewStatus.Ready) { return release1ShortNoticeReviewStatus; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1ShortNoticeReviewStatus.Ineligible; } if (state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")].State != Release1MissionState.Satisfied) { return Release1ShortNoticeReviewStatus.Ineligible; } Release1MissionRecord release1MissionRecord = Mission(state); Release1ShortNoticeAssignmentMode? release1ShortNoticeAssignmentMode = release1MissionRecord.State switch { Release1MissionState.Offered => Release1ShortNoticeAssignmentMode.Primary, Release1MissionState.MakeGoodOffered => Release1ShortNoticeAssignmentMode.MakeGood, Release1MissionState.RecoveryAvailable => Release1ShortNoticeAssignmentMode.Recovery, _ => null, }; if (!release1ShortNoticeAssignmentMode.HasValue) { return Release1ShortNoticeReviewStatus.Ineligible; } int attempt = ((release1ShortNoticeAssignmentMode == Release1ShortNoticeAssignmentMode.Primary) ? release1MissionRecord.Attempt : (release1MissionRecord.Attempt + 1)); Release1TransitionKind transitionKind = release1ShortNoticeAssignmentMode switch { Release1ShortNoticeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1ShortNoticeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1ShortNoticeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string value = Release1LogicalCorrelation.Create(context.PlayerId, "release1.short-notice", attempt, transitionKind, AcceptanceReceipt(release1ShortNoticeAssignmentMode.Value, attempt)).Value; if (_world.TryReadPackaging(Release1SmallCourtesyPackageKind.Brick, out Release1SmallCourtesyPackagingCandidate packaging) != Release1SmallCourtesyWorldReadStatus.Ready) { return Release1ShortNoticeReviewStatus.Unavailable; } try { packaging.Validate(); } catch (ArgumentException) { return Release1ShortNoticeReviewStatus.Unavailable; } IReadOnlyList products; if (release1ShortNoticeAssignmentMode == Release1ShortNoticeAssignmentMode.Primary) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadProducts(out products); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } } else { Release1ShortNoticeAssignment release1ShortNoticeAssignment = state.ShortNoticeAssignments.OrderBy((Release1ShortNoticeAssignment candidate) => candidate.Attempt).FirstOrDefault(); if ((object)release1ShortNoticeAssignment == null) { return Release1ShortNoticeReviewStatus.Ineligible; } products = new Release1SmallCourtesyProductCandidate[1] { new Release1SmallCourtesyProductCandidate(release1ShortNoticeAssignment.ProductId, release1ShortNoticeAssignment.ProductName, 0.0, IsDiscovered: true) }; } IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = _world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus2); } if (!Release1ShortNoticeAssignmentSelector.TrySelect(products, drops, release1ShortNoticeAssignmentMode.Value, attempt, value, packaging.PackagingId, packaging.PackagingName, Release1ShortNoticeValueConvention.PerStack, out Release1ShortNoticeAssignment assignment, out Release1ShortNoticeSelectionStatus status)) { return Map(status); } quote = new Release1ShortNoticeQuote(assignment, (release1ShortNoticeAssignmentMode == Release1ShortNoticeAssignmentMode.Recovery) ? ((double?)null) : new double?(12.0)); quote.Validate(); return Release1ShortNoticeReviewStatus.Ready; } private void RefreshOfferStatus() { if (_disposed) { OfferStatus = Release1ShortNoticeOfferStatus.Disposed; } else if (!_loadActive) { OfferStatus = Release1ShortNoticeOfferStatus.Inactive; } else if (_saving) { OfferStatus = Release1ShortNoticeOfferStatus.Saving; } else if (_dismissed) { OfferStatus = Release1ShortNoticeOfferStatus.Dismissed; } else { OfferStatus = Map(TryBuildQuote(out Release1ShortNoticeQuote _)); } } private Release1ShortNoticeReviewStatus ReadMatchingContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1ShortNoticeReviewStatus.Inactive; } try { Release1StoryHostContextSnapshot context2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadContext(out context2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!SameContext(snapshot, context2)) { return Release1ShortNoticeReviewStatus.Inactive; } context = snapshot; return Release1ShortNoticeReviewStatus.Ready; } catch { return Release1ShortNoticeReviewStatus.Unavailable; } } private bool TryReadGameHours(out double gameHours) { gameHours = 0.0; if (!TryReadGameMinutes(out var totalMinutes)) { return false; } gameHours = totalMinutes / 60.0; return double.IsFinite(gameHours); } private bool TryReadGameMinutes(out double totalMinutes) { totalMinutes = 0.0; try { if (_world.TryReadCanonicalTotalMinutes(out var totalMinutes2) != Release1SmallCourtesyWorldReadStatus.Ready || !double.IsFinite(totalMinutes2) || totalMinutes2 < 0.0) { return false; } totalMinutes = totalMinutes2; return true; } catch { return false; } } private Release1StoryCommand CreateCommand(Release1TransitionKind transition, int attempt, string receipt, string? termsVersion, double? acceptedHours, double? deadlineHours) { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { throw new InvalidOperationException("Story context was not active."); } return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, "release1.short-notice", attempt, transition, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.short-notice", attempt, transition, receipt).Value, termsVersion, acceptedHours, deadlineHours); } private void DismissForLoad() { _dismissed = true; _reviewedQuote = null; OfferStatus = Release1ShortNoticeOfferStatus.Dismissed; } private static Release1MissionRecord Mission(Release1StoryState state) { return state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; } private static string AcceptanceReceipt(Release1ShortNoticeAssignmentMode mode, int attempt) { return $"short-notice-{mode.ToString().ToLowerInvariant()}-accept-v1-a{attempt}"; } private static string DeadlineToken(Release1MissionState state) { if (state != Release1MissionState.Active) { return "make-good"; } return "primary"; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private static Release1ShortNoticeReviewResult Review(Release1ShortNoticeReviewStatus status, string message) { return new Release1ShortNoticeReviewResult(status, null, message); } private static Release1ShortNoticeDecisionResult Decision(Release1ShortNoticeDecisionStatus status, string message) { return new Release1ShortNoticeDecisionResult(status, message); } private static Release1ShortNoticeReviewStatus Map(Release1SmallCourtesyWorldReadStatus status) { if ((uint)(status - 1) <= 1u) { return Release1ShortNoticeReviewStatus.Pending; } return Release1ShortNoticeReviewStatus.Unavailable; } private static Release1ShortNoticeOfferStatus Map(Release1ShortNoticeReviewStatus status) { return status switch { Release1ShortNoticeReviewStatus.Ready => Release1ShortNoticeOfferStatus.Available, Release1ShortNoticeReviewStatus.Inactive => Release1ShortNoticeOfferStatus.Inactive, Release1ShortNoticeReviewStatus.Ineligible => Release1ShortNoticeOfferStatus.Ineligible, Release1ShortNoticeReviewStatus.Pending => Release1ShortNoticeOfferStatus.Pending, Release1ShortNoticeReviewStatus.Saving => Release1ShortNoticeOfferStatus.Saving, Release1ShortNoticeReviewStatus.NoDiscoveredProduct => Release1ShortNoticeOfferStatus.NoDiscoveredProduct, Release1ShortNoticeReviewStatus.NoEmptyDeadDrop => Release1ShortNoticeOfferStatus.NoEmptyDeadDrop, Release1ShortNoticeReviewStatus.Dismissed => Release1ShortNoticeOfferStatus.Dismissed, Release1ShortNoticeReviewStatus.Disposed => Release1ShortNoticeOfferStatus.Disposed, _ => Release1ShortNoticeOfferStatus.Unavailable, }; } private static Release1ShortNoticeReviewStatus Map(Release1ShortNoticeSelectionStatus status) { return status switch { Release1ShortNoticeSelectionStatus.NoDiscoveredProduct => Release1ShortNoticeReviewStatus.NoDiscoveredProduct, Release1ShortNoticeSelectionStatus.NoEmptyDeadDrop => Release1ShortNoticeReviewStatus.NoEmptyDeadDrop, _ => Release1ShortNoticeReviewStatus.Unavailable, }; } private static string Message(Release1ShortNoticeReviewStatus status) { return status switch { Release1ShortNoticeReviewStatus.NoDiscoveredProduct => "No discovered product is available for assignment.", Release1ShortNoticeReviewStatus.NoEmptyDeadDrop => "One clear dead drop is required before a hand off can be assigned.", Release1ShortNoticeReviewStatus.Pending => "World eligibility is still pending.", Release1ShortNoticeReviewStatus.Ineligible => "Short Notice is not currently offerable.", Release1ShortNoticeReviewStatus.Inactive => "The canonical story context is inactive.", _ => "Short Notice world data is unavailable.", }; } public Release1ShortNoticeObservationStatus ReconcileObservation() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1ShortNoticeReviewStatus.Ready) { return Release1ShortNoticeObservationStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord mission, out Release1ShortNoticeAssignment assignment, out string _)) { return Release1ShortNoticeObservationStatus.NoWork; } if (HasConsumptionEffect(mission.Attempt)) { return Release1ShortNoticeObservationStatus.NoWork; } if (!TryReadSlots(assignment.HandoffDropGuid, out IReadOnlyList slots)) { return Release1ShortNoticeObservationStatus.Unavailable; } if (slots.Count == 0) { ReportHoldOnce(assignment.Attempt, "the handoff drop reported no slots at all; it has not finished initializing."); return Release1ShortNoticeObservationStatus.Unavailable; } Release1ConsignmentFingerprint fingerprint = Fingerprint(assignment); Release1SmallCourtesySlotSnapshot single; int num = Release1ConsignmentClassifier.CountIdentityMatches(slots, fingerprint, out single); if (num > 1) { int attempt = assignment.Attempt; bool? spreadNoticed = true; SetProgress(attempt, null, null, spreadNoticed); return Release1ShortNoticeObservationStatus.Spread; } if (num == 0) { SetProgress(assignment.Attempt, 0); return Release1ShortNoticeObservationStatus.NoWork; } int quantity = single.Quantity; if (quantity >= assignment.RequiredQuantity) { SetProgress(assignment.Attempt, quantity); return Release1ShortNoticeObservationStatus.Ready; } SetProgress(assignment.Attempt, quantity, assignment.RequiredQuantity - quantity); return Release1ShortNoticeObservationStatus.Shortfall; } public Release1ShortNoticeObservationStatus TryHandleDropClosed(string deadDropGuid) { if (_disposed || !_loadActive || _saving) { return Release1ShortNoticeObservationStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord _, out Release1ShortNoticeAssignment assignment, out string _) || !string.Equals(assignment.HandoffDropGuid, deadDropGuid, StringComparison.Ordinal)) { return Release1ShortNoticeObservationStatus.NoWork; } Release1ShortNoticeObservationStatus num = ReconcileObservation(); if (num == Release1ShortNoticeObservationStatus.Ready) { ReconcileDeposit(); } return num; } public Release1ShortNoticeDepositStatus ReconcileDeposit() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1ShortNoticeReviewStatus.Ready) { return Release1ShortNoticeDepositStatus.NoWork; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1ShortNoticeDepositStatus.NoWork; } Release1MissionRecord release1MissionRecord = Mission(state); int attempt = release1MissionRecord.Attempt; Release1ShortNoticeAssignment release1ShortNoticeAssignment = state.ShortNoticeAssignments.SingleOrDefault((Release1ShortNoticeAssignment candidate) => candidate.Attempt == attempt); if ((object)release1ShortNoticeAssignment == null) { return Release1ShortNoticeDepositStatus.NoWork; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == ConsumptionEffectId(attempt)); if ((object)release1NativeEffectJournalEntry == null) { if (!TryGetActiveStage(out Release1MissionRecord _, out Release1ShortNoticeAssignment _, out string authorization)) { return Release1ShortNoticeDepositStatus.NoWork; } return BeginDeposit(release1MissionRecord, release1ShortNoticeAssignment, authorization); } return ContinueDeposit(release1MissionRecord, release1ShortNoticeAssignment, release1NativeEffectJournalEntry); } private Release1ShortNoticeDepositStatus BeginDeposit(Release1MissionRecord mission, Release1ShortNoticeAssignment assignment, string authorization) { if (!TryReadSlots(assignment.HandoffDropGuid, out IReadOnlyList slots)) { return Release1ShortNoticeDepositStatus.Rejected; } if (slots.Count == 0) { return Release1ShortNoticeDepositStatus.NoWork; } if (Release1ConsignmentClassifier.CountIdentityMatches(slots, Fingerprint(assignment), out Release1SmallCourtesySlotSnapshot single) != 1) { return Release1ShortNoticeDepositStatus.NoWork; } if (single.Quantity < assignment.RequiredQuantity) { return Release1ShortNoticeDepositStatus.NoWork; } Release1SmallCourtesyCargoIdentity release1SmallCourtesyCargoIdentity; try { release1SmallCourtesyCargoIdentity = new Release1SmallCourtesyCargoIdentity(assignment.ProductId, assignment.PackagingId, single.SlotIndex, single.Quantity, single.Quantity - assignment.RequiredQuantity, single.MonetaryValue); } catch (ArgumentException) { return Release1ShortNoticeDepositStatus.Rejected; } Release1NativeEffectJournalEntry prepared = new Release1NativeEffectJournalEntry(ConsumptionEffectId(assignment.Attempt), "release1.short-notice", assignment.Attempt, "CargoTransfer", assignment.HandoffDropGuid, "oc-short-notice", release1SmallCourtesyCargoIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, _story.State.Revision + 1, null, authorization, ExecutionBlocked: false, mission.Revision); if (!_story.TryPrepareNativeEffect(prepared).Accepted) { return Release1ShortNoticeDepositStatus.Rejected; } Release1NativeEffectJournalEntry effect = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == prepared.EffectId); if (!ConsumeManifest(assignment, release1SmallCourtesyCargoIdentity, effect)) { return Release1ShortNoticeDepositStatus.Ambiguous; } return FinishDeposit(Mission(_story.State), assignment, effect, release1SmallCourtesyCargoIdentity, authorization); } private Release1ShortNoticeDepositStatus ContinueDeposit(Release1MissionRecord mission, Release1ShortNoticeAssignment assignment, Release1NativeEffectJournalEntry effect) { string authorizedStoryCorrelationId = effect.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId) || !Release1SmallCourtesyCargoIdentity.TryParse(effect.AmountOrCargoIdentity, out Release1SmallCourtesyCargoIdentity identity) || (object)identity == null || effect.Attempt != mission.Attempt || !string.Equals(effect.EffectKind, "CargoTransfer", StringComparison.Ordinal) || !string.Equals(effect.SourceIdentity, assignment.HandoffDropGuid, StringComparison.Ordinal) || !string.Equals(effect.DestinationIdentity, "oc-short-notice", StringComparison.Ordinal) || !string.Equals(identity.ProductId, assignment.ProductId, StringComparison.Ordinal) || !string.Equals(identity.PackageId, assignment.PackagingId, StringComparison.Ordinal)) { return BlockConsumption(effect); } if (effect.ExecutionBlocked) { return Release1ShortNoticeDepositStatus.Ambiguous; } if (effect.Phase == Release1NativeEffectPhase.Prepared) { if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot)) { return Release1ShortNoticeDepositStatus.Rejected; } bool flag = MatchesCargoSlot(slot, identity, identity.PreQuantity); if (MatchesCargoSlot(slot, identity, identity.PostQuantity)) { if (!MarkConsumptionApplied(effect)) { return Release1ShortNoticeDepositStatus.Rejected; } } else { if (!flag) { return BlockConsumption(effect); } if (!ConsumeManifest(assignment, identity, effect)) { return Release1ShortNoticeDepositStatus.Ambiguous; } } } return FinishDeposit(mission, assignment, effect, identity, authorizedStoryCorrelationId); } private Release1ShortNoticeDepositStatus FinishDeposit(Release1MissionRecord mission, Release1ShortNoticeAssignment assignment, Release1NativeEffectJournalEntry effect, Release1SmallCourtesyCargoIdentity identity, string authorization) { long revision = _story.State.Revision; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effect.EffectId); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { return Release1ShortNoticeDepositStatus.Ambiguous; } if (!TryCompleteAndPrepareReward(release1NativeEffectJournalEntry, assignment, identity)) { return Release1ShortNoticeDepositStatus.Rejected; } Release1ShortNoticeDepositStatus release1ShortNoticeDepositStatus = ReconcileReward(assignment.Attempt, revision); if (release1ShortNoticeDepositStatus != Release1ShortNoticeDepositStatus.Paid && release1ShortNoticeDepositStatus != Release1ShortNoticeDepositStatus.Committed && release1ShortNoticeDepositStatus != Release1ShortNoticeDepositStatus.AwaitingAppliedSave) { return release1ShortNoticeDepositStatus; } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied && revision <= _story.LastPersistedRevision) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorization, revision); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(assignment.Attempt, "the consumption commit after a save was rejected: " + release1StoryRuntimeCommandResult.Message); } } return release1ShortNoticeDepositStatus switch { Release1ShortNoticeDepositStatus.Paid => Release1ShortNoticeDepositStatus.Paid, Release1ShortNoticeDepositStatus.Committed => Release1ShortNoticeDepositStatus.Committed, _ => Release1ShortNoticeDepositStatus.AwaitingAppliedSave, }; } private bool ConsumeManifest(Release1ShortNoticeAssignment assignment, Release1SmallCourtesyCargoIdentity identity, Release1NativeEffectJournalEntry effect) { if (!TryAcquireSlotLock(assignment.HandoffDropGuid, identity.SlotIndex)) { BlockConsumption(effect); return false; } try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeSlotQuantity(assignment.HandoffDropGuid, identity.SlotIndex, -identity.ConsumedCount); } catch { BlockConsumption(effect); return false; } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { BlockConsumption(effect); return false; } if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot) || !MatchesCargoSlot(slot, identity, identity.PostQuantity) || !Release1ShortNoticeValueRule.AgreesWithObservation(assignment.ValueConvention, identity.PreQuantity, identity.DepositedMonetaryValue, slot.Quantity, slot.MonetaryValue)) { BlockConsumption(effect); return false; } return MarkConsumptionApplied(effect); } finally { ReleaseSlotLock(); } } private bool MarkConsumptionApplied(Release1NativeEffectJournalEntry effect) { return _story.TryMarkNativeEffectApplied(effect.EffectId, $"short-notice-cargo-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted; } private bool TryCompleteAndPrepareReward(Release1NativeEffectJournalEntry consumption, Release1ShortNoticeAssignment assignment, Release1SmallCourtesyCargoIdentity identity) { Release1StoryState state = _story.State; if ((object)state == null) { return false; } Release1MissionRecord mission = Mission(state); Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry effect) => effect.EffectId == RewardEffectId(mission.Attempt)); if (mission.State == Release1MissionState.Satisfied) { return (object)release1NativeEffectJournalEntry != null; } Release1MissionState state2 = mission.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive || state2 == Release1MissionState.RecoveryActive) ? true : false); if (!flag || consumption.Phase != Release1NativeEffectPhase.Applied || consumption.ExecutionBlocked || consumption.Attempt != mission.Attempt || (object)release1NativeEffectJournalEntry != null || !TryReadCashBalance(out var balance)) { return false; } if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot)) { return false; } float? num = Release1ShortNoticeValueRule.SummedConsumedValue(assignment.ValueConvention, identity.PreQuantity, identity.DepositedMonetaryValue, slot.Quantity, slot.MonetaryValue, identity.ConsumedCount); if (num.HasValue) { float valueOrDefault = num.GetValueOrDefault(); float? num2 = Release1RewardMath.QuoteWholeDollars(valueOrDefault, (decimal)assignment.RewardMultiplier); if (num2.HasValue) { float valueOrDefault2 = num2.GetValueOrDefault(); Release1SmallCourtesyRewardIdentity release1SmallCourtesyRewardIdentity; try { release1SmallCourtesyRewardIdentity = new Release1SmallCourtesyRewardIdentity(balance, valueOrDefault2, balance + valueOrDefault2); } catch (ArgumentException) { return false; } if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return false; } string receiptId = $"short-notice-complete-v1-a{mission.Attempt}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.short-notice", mission.Attempt, Release1TransitionKind.MissionCompleted, receiptId).Value; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = new Release1NativeEffectJournalEntry(RewardEffectId(mission.Attempt), "release1.short-notice", mission.Attempt, "Reward", "oc-short-notice", "player-cash", release1SmallCourtesyRewardIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, state.Revision + 1, null, value, ExecutionBlocked: false, mission.Revision); Guid sessionEpoch = snapshot.SessionEpoch; long loadEpoch = snapshot.LoadEpoch; string playerId = snapshot.PlayerId; int attempt = mission.Attempt; Release1CompletionTiming? completionTiming = ((mission.State != Release1MissionState.Active) ? Release1CompletionTiming.Late : Release1CompletionTiming.OnTime); string rewardAuthorizationReceiptId = $"short-notice-reward-auth-v1-a{mission.Attempt}"; Release1NativeEffectJournalEntry preparedNativeEffect = release1NativeEffectJournalEntry2; Release1StoryCommand command = new Release1StoryCommand(sessionEpoch, loadEpoch, playerId, "release1.short-notice", attempt, Release1TransitionKind.MissionCompleted, receiptId, value, null, null, null, completionTiming, rewardAuthorizationReceiptId, null, null, preparedNativeEffect); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecute(command); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(mission.Attempt, "completing the mission after the manifest was consumed was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } return false; } return false; } private Release1ShortNoticeDepositStatus ReconcileReward(int attempt, long capturedRevision) { Release1StoryState? state = _story.State; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state?.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == RewardEffectId(attempt)); if ((object)state == null || (object)release1NativeEffectJournalEntry == null) { return Release1ShortNoticeDepositStatus.Rejected; } if (!Release1SmallCourtesyRewardIdentity.TryParse(release1NativeEffectJournalEntry.AmountOrCargoIdentity, out Release1SmallCourtesyRewardIdentity identity) || (object)identity == null || !string.Equals(release1NativeEffectJournalEntry.EffectKind, "Reward", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.SourceIdentity, "oc-short-notice", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.DestinationIdentity, "player-cash", StringComparison.Ordinal)) { return BlockReward(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.ExecutionBlocked) { return Release1ShortNoticeDepositStatus.Ambiguous; } if (!TryReadCashBalance(out var balance)) { return Release1ShortNoticeDepositStatus.Rejected; } bool flag = WithinCashTolerance(balance, identity.BaselineCash, identity.VerificationTolerance); bool flag2 = WithinCashTolerance(balance, identity.ExpectedCash, identity.VerificationTolerance); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { if (flag2) { return MarkRewardApplied(release1NativeEffectJournalEntry); } if (!flag) { return BlockReward(release1NativeEffectJournalEntry); } Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeCashBalance(identity.WholeDollarAmount); } catch { return BlockReward(release1NativeEffectJournalEntry); } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return BlockReward(release1NativeEffectJournalEntry); } if (!TryReadCashBalance(out var balance2) || !WithinCashTolerance(balance2, identity.ExpectedCash, identity.VerificationTolerance)) { return BlockReward(release1NativeEffectJournalEntry); } return MarkRewardApplied(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied) { if (!flag2) { return Release1ShortNoticeDepositStatus.Ambiguous; } if (capturedRevision > _story.LastPersistedRevision) { return Release1ShortNoticeDepositStatus.AwaitingAppliedSave; } string authorizedStoryCorrelationId = release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId)) { return Release1ShortNoticeDepositStatus.Ambiguous; } if (!_story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorizedStoryCorrelationId, capturedRevision).Accepted) { return Release1ShortNoticeDepositStatus.Rejected; } return Release1ShortNoticeDepositStatus.Committed; } return Release1ShortNoticeDepositStatus.Committed; } private Release1ShortNoticeDepositStatus MarkRewardApplied(Release1NativeEffectJournalEntry effect) { if (!_story.TryMarkNativeEffectApplied(effect.EffectId, $"short-notice-reward-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted) { return Release1ShortNoticeDepositStatus.Rejected; } return Release1ShortNoticeDepositStatus.Paid; } private Release1ShortNoticeDepositStatus BlockConsumption(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } ReleaseSlotLock(); return Release1ShortNoticeDepositStatus.Ambiguous; } private Release1ShortNoticeDepositStatus BlockReward(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } return Release1ShortNoticeDepositStatus.Ambiguous; } private bool TryReadCashBalance(out float balance) { balance = 0f; try { return _world.TryReadCashBalance(out balance) == Release1SmallCourtesyWorldReadStatus.Ready && float.IsFinite(balance) && balance >= 0f; } catch { return false; } } private bool TryAcquireSlotLock(string deadDropGuid, int slotIndex) { if (_lockedDropGuid != null) { if (string.Equals(_lockedDropGuid, deadDropGuid, StringComparison.Ordinal)) { return _lockedSlotIndex == slotIndex; } return false; } _lockedDropGuid = deadDropGuid; _lockedSlotIndex = slotIndex; try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = _world.TrySetSlotLocked(deadDropGuid, slotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { if (release1SmallCourtesyWorldMutationStatus == Release1SmallCourtesyWorldMutationStatus.Ambiguous) { ReleaseSlotLock(); } else { _lockedDropGuid = null; _lockedSlotIndex = -1; } return false; } return true; } catch { ReleaseSlotLock(); return false; } } private void ReleaseSlotLock() { if (_lockedDropGuid == null || _lockedSlotIndex < 0) { return; } try { if (_world.TrySetSlotLocked(_lockedDropGuid, _lockedSlotIndex, locked: false) == Release1SmallCourtesyWorldMutationStatus.Succeeded) { _lockedDropGuid = null; _lockedSlotIndex = -1; } } catch { } } private static bool WithinCashTolerance(float actual, float expected, float tolerance) { if (float.IsFinite(actual)) { return MathF.Abs(actual - expected) <= tolerance; } return false; } private static bool MatchesCargoSlot(Release1SmallCourtesySlotSnapshot slot, Release1SmallCourtesyCargoIdentity identity, int expectedQuantity) { if (slot.SlotIndex != identity.SlotIndex || slot.Quantity != expectedQuantity) { return false; } if (expectedQuantity == 0) { return true; } if (slot.IsPackaged && string.Equals(slot.ProductId, identity.ProductId, StringComparison.Ordinal)) { return string.Equals(slot.PackagingId, identity.PackageId, StringComparison.Ordinal); } return false; } private bool TryReadSlot(string deadDropGuid, int slotIndex, out Release1SmallCourtesySlotSnapshot slot) { slot = null; if (!TryReadSlots(deadDropGuid, out IReadOnlyList slots)) { return false; } slot = slots.SingleOrDefault((Release1SmallCourtesySlotSnapshot candidate) => candidate.SlotIndex == slotIndex); return (object)slot != null; } private static string RewardEffectId(int attempt) { return $"short-notice-reward-v1-a{attempt}"; } private void TryQueueArthur() { if (_phone == null || _disposed || !_loadActive || _saving) { return; } Release1StoryState state = _story.State; if ((object)state == null || !_story.TryGetActiveContext(out var snapshot, out var _)) { return; } Release1MissionRecord release1MissionRecord = Mission(state); if (release1MissionRecord.State != Release1MissionState.MakeGoodOffered || release1MissionRecord.LastOutcome != Release1MissionOutcome.RequiredFailure) { return; } try { Release1PhoneCallRequest request = Release1PhoneCallRequest.Create(snapshot, "release1.short-notice", release1MissionRecord.Attempt, Release1PhoneCallCorrelation.Create(snapshot.PlayerId, "release1.short-notice", release1MissionRecord.Attempt, Release1PhoneCallRole.Arthur, "sn-warning-v1"), Release1PhoneCallRole.Arthur, "Arthur Selby", ArthurWarningStages, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.short-notice", release1MissionRecord.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-sn-accepted-v1").Value); _phone.TryQueue(request); } catch (Exception ex) { _log("Short Notice Arthur request was not published: " + ex.GetType().Name); } } private void RefreshDropBinding() { if (!_loadActive || _disposed || !TryGetActiveStage(out Release1MissionRecord _, out Release1ShortNoticeAssignment assignment, out string _)) { DisposeDropSubscription(); return; } string handoffDropGuid = assignment.HandoffDropGuid; if (_dropSubscription != null && string.Equals(_boundDropGuid, handoffDropGuid, StringComparison.Ordinal)) { return; } DisposeDropSubscription(); try { if (_world.TrySubscribeDeadDropClosed(handoffDropGuid, OnDropClosed, out IRelease1SmallCourtesyDropSubscription subscription) != Release1SmallCourtesyWorldReadStatus.Ready || subscription == null || !string.Equals(subscription.DeadDropGuid, handoffDropGuid, StringComparison.Ordinal)) { subscription?.Dispose(); return; } _dropSubscription = subscription; _boundDropGuid = handoffDropGuid; } catch { DisposeDropSubscription(); } } private void OnDropClosed(string deadDropGuid) { TryHandleDropClosed(deadDropGuid); } private void DisposeDropSubscription() { try { _dropSubscription?.Dispose(); } catch { } _dropSubscription = null; _boundDropGuid = null; } private void Converge() { RefreshDropBinding(); ReconcileObservation(); ReconcileDeposit(); TryQueueArthur(); } internal static Release1ShortNoticeAssignmentMode? ExpectedAssignmentMode(Release1MissionState missionState) { switch (missionState) { case Release1MissionState.Accepted: case Release1MissionState.Active: return Release1ShortNoticeAssignmentMode.Primary; case Release1MissionState.MakeGoodOffered: case Release1MissionState.MakeGoodActive: return Release1ShortNoticeAssignmentMode.MakeGood; case Release1MissionState.RecoveryAvailable: case Release1MissionState.RecoveryActive: return Release1ShortNoticeAssignmentMode.Recovery; default: return null; } } private bool TryGetActiveStage(out Release1MissionRecord mission, out Release1ShortNoticeAssignment assignment, out string authorization) { mission = null; assignment = null; authorization = string.Empty; Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return false; } mission = Mission(state); Release1ShortNoticeAssignmentMode? expectedMode = ExpectedAssignmentMode(mission.State); if (!expectedMode.HasValue) { return false; } int missionAttempt = mission.Attempt; assignment = state.ShortNoticeAssignments.SingleOrDefault((Release1ShortNoticeAssignment candidate) => candidate.Attempt == missionAttempt && candidate.Mode == expectedMode.Value); if ((object)assignment == null) { return false; } authorization = ((expectedMode == Release1ShortNoticeAssignmentMode.Primary) ? (mission.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) && correlation.TransitionKind == Release1TransitionKind.MissionActivated) ?? string.Empty) : assignment.AuthorizationCorrelationId); return !string.IsNullOrEmpty(authorization); } private bool HasConsumptionEffect(int attempt) { return _story.State?.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.EffectId == ConsumptionEffectId(attempt)) ?? false; } private static string ConsumptionEffectId(int attempt) { return $"short-notice-cargo-v1-a{attempt}"; } private Release1ShortNoticeProgress? Progress(int attempt) { return _story.State?.ShortNoticeProgress.SingleOrDefault((Release1ShortNoticeProgress progress) => progress.Attempt == attempt); } private bool SetProgress(int attempt, int? observedQuantity = null, Option? lastShortfallNoticed = null, bool? spreadNoticed = null) { Release1ShortNoticeProgress release1ShortNoticeProgress = Progress(attempt) ?? Release1ShortNoticeProgress.Fresh(attempt); Release1ShortNoticeProgress release1ShortNoticeProgress2 = release1ShortNoticeProgress with { ObservedQuantity = (observedQuantity ?? release1ShortNoticeProgress.ObservedQuantity), LastShortfallNoticed = ((!lastShortfallNoticed.HasValue) ? release1ShortNoticeProgress.LastShortfallNoticed : lastShortfallNoticed.GetValueOrDefault().Value), SpreadNoticed = (spreadNoticed ?? release1ShortNoticeProgress.SpreadNoticed) }; if (release1ShortNoticeProgress2 == release1ShortNoticeProgress) { return true; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TrySetShortNoticeProgress(release1ShortNoticeProgress2); if (!release1StoryRuntimeCommandResult.Accepted && release1StoryRuntimeCommandResult.Status != Release1StoryCommandStatus.NoOp) { ReportHoldOnce(attempt, "recording the deposit progress was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } private bool TryReadSlots(string deadDropGuid, out IReadOnlyList slots) { slots = Array.Empty(); try { if (_world.TryReadDeadDropSlots(deadDropGuid, out IReadOnlyList slots2) != Release1SmallCourtesyWorldReadStatus.Ready || slots2 == null) { return false; } Release1SmallCourtesySlotSnapshot[] array = slots2.ToArray(); Release1SmallCourtesySlotSnapshot[] array2 = array; for (int i = 0; i < array2.Length; i++) { array2[i].Validate(); } if (array.Select((Release1SmallCourtesySlotSnapshot slot) => slot.SlotIndex).Distinct().Count() != array.Length) { return false; } slots = array; return true; } catch { return false; } } private void ReportHoldOnce(int attempt, string message) { if (_reportedHolds.Add($"a{attempt}:{message}")) { _log($"Short Notice attempt {attempt} is holding: {message}"); } } private static Release1ConsignmentFingerprint Fingerprint(Release1ShortNoticeAssignment assignment) { return new Release1ConsignmentFingerprint(assignment.ProductId, assignment.PackagingId, assignment.RequiredQuantity); } } public enum Release1ShortNoticeCardStage { Hidden, Offer, Review, NoDrop, PersistenceDeferred, DecisionRejected, AwaitingActivation, Active, Shortfall, Spread, Ambiguous, Completed } public sealed record Release1ShortNoticeViewModel(bool Visible, Release1ShortNoticeCardStage Stage, string Title, string Body, bool CanReview, bool CanAccept, bool CanDefer); public static class Release1ShortNoticePresentation { public const string Title = "Short Notice"; public const string OfferText = "I need product tonight, not next week. One drop, one slot, and a window that closes fast. Do you have the balls?"; public const string RefusalText = "I need one clear drop before I can set this up. Try me again when the town has room."; public const string QuestionText = "Are you taking it?"; public const string SourceText = "It comes out of your own stock. I am not sending you any. Prove your own worth, I'm not holding your hand like a baby."; public const string PaymentText = "I pay 175 percent of what you leave, the moment it lands. Be happy I give you fucking anything."; public const string ActiveStatusText = "Leave the whole order in one slot at the drop before the window closes."; public const string ShortfallStatusManyFormat = "Part of the order is there. {0} more still needed."; public const string ShortfallStatusOneText = "Part of the order is there. One more still needed."; public const string SpreadStatusText = "The order is in more than one slot. Put it together."; public const string CompletedStatusText = "Complete. Delivered and paid."; public const string AmbiguousStatusText = "Stopped on conflicting evidence. It will not retry."; public static string Units(int count, string packagingName) { if (count == 1) { return $"{count} {packagingName}"; } return $"{count} {packagingName}s"; } public static Release1ShortNoticeViewModel Build(Release1StoryState? state, Release1ShortNoticeOfferStatus offerStatus, Release1ShortNoticeQuote? reviewedQuote, Release1ShortNoticeProgress? progress, string? feedback = null, Release1ShortNoticeCardStage? feedbackStage = null) { if (!string.IsNullOrWhiteSpace(feedback)) { return Visible(feedbackStage ?? Release1ShortNoticeCardStage.PersistenceDeferred, Copy(feedback)); } if (offerStatus == Release1ShortNoticeOfferStatus.NoEmptyDeadDrop) { return new Release1ShortNoticeViewModel(Visible: true, Release1ShortNoticeCardStage.NoDrop, "Short Notice", Copy("I need one clear drop before I can set this up. Try me again when the town has room."), CanReview: true, CanAccept: false, CanDefer: false); } if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Hidden(); } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; Release1ShortNoticeAssignmentMode? expectedMode = Release1ShortNoticeMissionService.ExpectedAssignmentMode(mission.State); Release1ShortNoticeAssignment release1ShortNoticeAssignment = state.ShortNoticeAssignments.SingleOrDefault((Release1ShortNoticeAssignment candidate) => candidate.Attempt == mission.Attempt && (!expectedMode.HasValue || candidate.Mode == expectedMode.Value)); if ((object)release1ShortNoticeAssignment == null) { if ((object)reviewedQuote != null) { return Terms(reviewedQuote.Assignment, reviewedQuote.DeadlineDurationHours); } if (offerStatus == Release1ShortNoticeOfferStatus.Available) { return new Release1ShortNoticeViewModel(Visible: true, Release1ShortNoticeCardStage.Offer, "Short Notice", Copy("I need product tonight, not next week. One drop, one slot, and a window that closes fast. Do you have the balls?"), CanReview: true, CanAccept: false, CanDefer: false); } return Hidden(); } if (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.short-notice" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked)) { return Active(release1ShortNoticeAssignment, mission, Release1ShortNoticeCardStage.Ambiguous, "Stopped on conflicting evidence. It will not retry."); } if (mission.State == Release1MissionState.Satisfied) { return Active(release1ShortNoticeAssignment, mission, Release1ShortNoticeCardStage.Completed, "Complete. Delivered and paid."); } if (mission.State == Release1MissionState.Accepted) { return Active(release1ShortNoticeAssignment, mission, Release1ShortNoticeCardStage.AwaitingActivation, "Leave the whole order in one slot at the drop before the window closes."); } if ((object)progress != null && progress.SpreadNoticed) { return Active(release1ShortNoticeAssignment, mission, Release1ShortNoticeCardStage.Spread, "The order is in more than one slot. Put it together."); } int? num = progress?.LastShortfallNoticed; if (num.HasValue) { int valueOrDefault = num.GetValueOrDefault(); return Active(release1ShortNoticeAssignment, mission, Release1ShortNoticeCardStage.Shortfall, (valueOrDefault == 1) ? "Part of the order is there. One more still needed." : $"Part of the order is there. {valueOrDefault} more still needed."); } return Active(release1ShortNoticeAssignment, mission, Release1ShortNoticeCardStage.Active, "Leave the whole order in one slot at the drop before the window closes."); } private static Release1ShortNoticeViewModel Terms(Release1ShortNoticeAssignment assignment, double? deadlineHours) { return new Release1ShortNoticeViewModel(Visible: true, Release1ShortNoticeCardStage.Review, "Short Notice", Copy(Release1PlayerCopy.JoinLines(Release1PlayerCopy.AttemptHeader(assignment.Mode, assignment.Attempt), Units(assignment.RequiredQuantity, assignment.PackagingName) + " of " + assignment.ProductName, "Drop: " + assignment.HandoffDropName + ". All of it in one slot. It's not that complicated.", "Deadline: " + Release1PlayerCopy.Deadline(deadlineHours), "I pay 175 percent of what you leave, the moment it lands. Be happy I give you fucking anything.", "It comes out of your own stock. I am not sending you any. Prove your own worth, I'm not holding your hand like a baby.", "Are you taking it?")), CanReview: false, CanAccept: true, CanDefer: true); } private static Release1ShortNoticeViewModel Active(Release1ShortNoticeAssignment assignment, Release1MissionRecord mission, Release1ShortNoticeCardStage stage, string status) { return new Release1ShortNoticeViewModel(Visible: true, stage, "Short Notice", Copy(string.Join("\n", "Status: " + status, Units(assignment.RequiredQuantity, assignment.PackagingName) + " of " + assignment.ProductName, "Drop: " + assignment.HandoffDropName, "Deadline: " + Release1PlayerCopy.Deadline(DeadlineDuration(mission)), "I pay 175 percent of what you leave, the moment it lands. Be happy I give you fucking anything.")), CanReview: false, CanAccept: false, CanDefer: false); } private static double? DeadlineDuration(Release1MissionRecord mission) { if (mission.DeadlineGameTimeHours.HasValue && mission.AcceptedGameTimeHours.HasValue) { return mission.DeadlineGameTimeHours.Value - mission.AcceptedGameTimeHours.Value; } return null; } private static Release1ShortNoticeViewModel Visible(Release1ShortNoticeCardStage stage, string body) { return new Release1ShortNoticeViewModel(Visible: true, stage, "Short Notice", body, CanReview: false, CanAccept: false, CanDefer: false); } private static Release1ShortNoticeViewModel Hidden() { return new Release1ShortNoticeViewModel(Visible: false, Release1ShortNoticeCardStage.Hidden, "Short Notice", string.Empty, CanReview: false, CanAccept: false, CanDefer: false); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1ShortNoticePresenter : IDisposable { private readonly Release1ShortNoticeMissionService _service; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private string? _feedback; private Release1ShortNoticeCardStage? _feedbackStage; private bool _disposed; public Release1ShortNoticeViewModel View { get { if (!_disposed) { return Release1ShortNoticePresentation.Build(_story.State, _service.OfferStatus, _service.ReviewedQuote, CurrentProgress(), _feedback, _feedbackStage); } return Release1ShortNoticePresentation.Build(null, Release1ShortNoticeOfferStatus.Disposed, null, null); } } public Release1ShortNoticePresenter(Release1ShortNoticeMissionService service, Release1StoryRuntimeService story, Action? log = null) { _service = service ?? throw new ArgumentNullException("service"); _story = story ?? throw new ArgumentNullException("story"); _log = log; } public Release1ShortNoticeReviewResult TryReview() { Release1ShortNoticeReviewResult release1ShortNoticeReviewResult = _service.TryReview(); _feedback = ((release1ShortNoticeReviewResult.Status == Release1ShortNoticeReviewStatus.Ready) ? null : release1ShortNoticeReviewResult.Message); _feedbackStage = ((_feedback == null) ? ((Release1ShortNoticeCardStage?)null) : new Release1ShortNoticeCardStage?((release1ShortNoticeReviewResult.Status == Release1ShortNoticeReviewStatus.NoEmptyDeadDrop) ? Release1ShortNoticeCardStage.NoDrop : Release1ShortNoticeCardStage.DecisionRejected)); if (_feedback != null) { _log?.Invoke($"Short Notice review failed: {release1ShortNoticeReviewResult.Status}, {release1ShortNoticeReviewResult.Message}"); } return release1ShortNoticeReviewResult; } public Release1ShortNoticeDecisionResult TryAccept() { return Handle(_service.TryAccept()); } public Release1ShortNoticeDecisionResult TryDefer() { return Handle(_service.TryDefer()); } public void ClearFeedback() { _feedback = null; _feedbackStage = null; } public void Dispose() { _feedback = null; _feedbackStage = null; _disposed = true; } private Release1ShortNoticeProgress? CurrentProgress() { Release1StoryState state = _story.State; if ((object)state == null) { return null; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; return state.ShortNoticeProgress.SingleOrDefault((Release1ShortNoticeProgress progress) => progress.Attempt == mission.Attempt); } private Release1ShortNoticeDecisionResult Handle(Release1ShortNoticeDecisionResult result) { Release1ShortNoticeDecisionStatus status = result.Status; if ((uint)status <= 2u) { _feedback = null; _feedbackStage = null; } else if (result.Status == Release1ShortNoticeDecisionStatus.NoEmptyDeadDrop) { _feedback = null; _feedbackStage = null; } else { _feedback = result.Message; _feedbackStage = ((result.Status == Release1ShortNoticeDecisionStatus.PersistenceDeferred) ? Release1ShortNoticeCardStage.PersistenceDeferred : Release1ShortNoticeCardStage.DecisionRejected); _log?.Invoke($"Short Notice decision failed: {result.Status}, {result.Message}"); } return result; } } public static class Release1ShortNoticeQuantityHarness { public const int DecrementAmount = 2; public const string PerUnitLine = "the slot value did not change with the quantity, so the read is per unit."; public const string PerStackLine = "the slot value changed with the quantity, so the read is per stack."; public static Release1StagingHarnessResult TryDecrementByTwo(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); string text = null; int num = -1; try { IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"dead drops could not be read, status {release1SmallCourtesyWorldReadStatus}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), list); } foreach (Release1SmallCourtesyDropCandidate item in (drops ?? Array.Empty()).Where((Release1SmallCourtesyDropCandidate candidate) => (object)candidate != null).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal)) { IReadOnlyList slots; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = world.TryReadDeadDropSlots(item.Guid, out slots); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"drop {item.Name} ({item.Guid}) slots {release1SmallCourtesyWorldReadStatus2}"); continue; } Release1SmallCourtesySlotSnapshot chosen = (from slot in slots ?? Array.Empty() where (object)slot != null && slot.Quantity >= 2 orderby slot.SlotIndex select slot).FirstOrDefault(); if ((object)chosen == null) { continue; } list.Add($"drop {item.Name} ({item.Guid}) slot {chosen.SlotIndex} pre product {chosen.ProductId} packaging {chosen.PackagingId} quantity {chosen.Quantity} value {chosen.MonetaryValue}"); Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = world.TrySetSlotLocked(item.Guid, chosen.SlotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { list.Add($"the slot could not be locked, status {release1SmallCourtesyWorldMutationStatus}."); return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus), list); } text = item.Guid; num = chosen.SlotIndex; Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus2 = world.TryChangeSlotQuantity(item.Guid, chosen.SlotIndex, -2); list.Add($"decrement by {2} result {release1SmallCourtesyWorldMutationStatus2}"); if (release1SmallCourtesyWorldMutationStatus2 != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus2), list); } IReadOnlyList slots2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus3 = world.TryReadDeadDropSlots(item.Guid, out slots2); if (release1SmallCourtesyWorldReadStatus3 != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"the drop could not be read back, status {release1SmallCourtesyWorldReadStatus3}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus3), list); } Release1SmallCourtesySlotSnapshot release1SmallCourtesySlotSnapshot = (slots2 ?? Array.Empty()).FirstOrDefault((Release1SmallCourtesySlotSnapshot slot) => (object)slot != null && slot.SlotIndex == chosen.SlotIndex); if ((object)release1SmallCourtesySlotSnapshot == null) { list.Add("the slot disappeared from the read back."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } list.Add($"drop {item.Name} ({item.Guid}) slot {release1SmallCourtesySlotSnapshot.SlotIndex} post product {release1SmallCourtesySlotSnapshot.ProductId} packaging {release1SmallCourtesySlotSnapshot.PackagingId} quantity {release1SmallCourtesySlotSnapshot.Quantity} value {release1SmallCourtesySlotSnapshot.MonetaryValue}"); list.Add($"expected post quantity {chosen.Quantity - 2}, observed {release1SmallCourtesySlotSnapshot.Quantity}"); if (release1SmallCourtesySlotSnapshot.Quantity > 0) { list.Add((release1SmallCourtesySlotSnapshot.MonetaryValue == chosen.MonetaryValue) ? "the slot value did not change with the quantity, so the read is per unit." : "the slot value changed with the quantity, so the read is per stack."); } return new Release1StagingHarnessResult((release1SmallCourtesySlotSnapshot.Quantity != chosen.Quantity - 2) ? Release1StagingHarnessStatus.Faulted : Release1StagingHarnessStatus.Succeeded, list); } list.Add($"no dead drop slot held at least {2} units."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } catch (Exception ex) { list.Add("an exception was thrown while decrementing: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } finally { if (text != null && num >= 0) { try { world.TrySetSlotLocked(text, num, locked: false); } catch (Exception) { } } } } private static Release1StagingHarnessStatus MapReadStatus(Release1SmallCourtesyWorldReadStatus status) { return status switch { Release1SmallCourtesyWorldReadStatus.NotAuthoritative => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldReadStatus.Pending => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } private static Release1StagingHarnessStatus MapMutationStatus(Release1SmallCourtesyWorldMutationStatus status) { return status switch { Release1SmallCourtesyWorldMutationStatus.Succeeded => Release1StagingHarnessStatus.Succeeded, Release1SmallCourtesyWorldMutationStatus.Rejected => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldMutationStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } } public sealed class Release1ShortNoticeQuest : Quest { public const string DisplayTitle = "Short Notice"; private readonly QuestEntry[] _entries; protected override string Title => Release1PlayerCopy.Normalize("Short Notice"); protected override string Description => Release1PlayerCopy.Normalize("Short Notice"); public Release1ShortNoticeQuest() { string text = Release1PlayerCopy.Normalize("Short Notice"); _entries = (QuestEntry[])(object)new QuestEntry[1] { ((Quest)this).AddEntry(text, (Vector3?)null) }; } public void Project(Release1DesiredQuest quest) { ArgumentNullException.ThrowIfNull(quest, "quest"); if (quest.Entries.Count != _entries.Length) { throw new InvalidOperationException($"Release 1 Short Notice quest desired entry count ({quest.Entries.Count}) does not match the quest's native entry count ({_entries.Length})."); } for (int i = 0; i < _entries.Length; i++) { ApplyEntry(_entries[i], quest.Entries[i]); } } public IReadOnlyList ReadEntryStates() { return _entries.Select((QuestEntry entry) => MapState(entry.State)).ToArray(); } private static void ApplyEntry(QuestEntry entry, Release1DesiredEntry desired) { //IL_0035: 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) entry.Title = Release1PlayerCopy.Normalize(desired.Text); Release1WorldPoint marker = desired.Marker; entry.POIPosition = (Vector3)(((object)marker != null) ? new Vector3(marker.X, marker.Y, marker.Z) : Vector3.zero); switch (Release1NativePresentationSupport.ClassifyEntryAction(desired.State)) { case Release1QuestEntryProjectionAction.Begin: entry.Begin(); break; case Release1QuestEntryProjectionAction.Complete: entry.Complete(); break; default: entry.SetState((QuestState)0); break; } } private static Release1DesiredEntryState MapState(QuestState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 1) { if ((int)state == 2) { return Release1DesiredEntryState.Complete; } return Release1DesiredEntryState.Inactive; } return Release1DesiredEntryState.Active; } } public enum Release1ShortNoticeValueConvention { PerUnit, PerStack } public static class Release1ShortNoticeValueRule { public const Release1ShortNoticeValueConvention Observed = Release1ShortNoticeValueConvention.PerStack; private const float ReconciliationTolerance = 0.005f; private const float ReconciliationToleranceFloor = 0.01f; private static bool NearlyEqual(float a, float b) { float num = MathF.Max(0.005f * MathF.Max(MathF.Abs(a), MathF.Abs(b)), 0.01f); return MathF.Abs(a - b) <= num; } public static float? SummedConsumedValue(Release1ShortNoticeValueConvention convention, int preQuantity, float preValue, int postQuantity, float postValue, int consumedCount) { if (!Enum.IsDefined(convention)) { return null; } if (consumedCount < 1 || preQuantity < consumedCount || postQuantity < 0) { return null; } if (postQuantity != preQuantity - consumedCount) { return null; } if (!float.IsFinite(preValue) || preValue < 0f || !float.IsFinite(postValue) || postValue < 0f) { return null; } if (postQuantity == 0 && !NearlyEqual(postValue, 0f)) { return null; } float num = ((convention == Release1ShortNoticeValueConvention.PerUnit) ? ((float)consumedCount * preValue) : (preValue - postValue)); if (!float.IsFinite(num) || !(num >= 0f)) { return null; } return num; } public static bool AgreesWithObservation(Release1ShortNoticeValueConvention convention, int preQuantity, float preValue, int postQuantity, float postValue) { if (!Enum.IsDefined(convention)) { return false; } if (preQuantity < 1 || postQuantity < 0 || postQuantity >= preQuantity) { return false; } if (!float.IsFinite(preValue) || preValue < 0f || !float.IsFinite(postValue) || postValue < 0f) { return false; } if (postQuantity == 0) { if (convention == Release1ShortNoticeValueConvention.PerUnit) { return false; } if (NearlyEqual(postValue, 0f)) { return preValue - postValue > 0f; } return false; } if (convention != Release1ShortNoticeValueConvention.PerUnit) { if (postValue < preValue) { return postValue > 0f; } return false; } return NearlyEqual(postValue, preValue); } } public sealed class Release1SmallCourtesyComposition : IDisposable { private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private readonly HashSet _reportedAmbiguities = new HashSet(StringComparer.Ordinal); private bool _disposed; public Release1SmallCourtesyMissionService Service { get; } public Release1SmallCourtesyPresenter Presenter { get; } public Release1SmallCourtesyComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _log = log; Service = new Release1SmallCourtesyMissionService(story, world); Presenter = new Release1SmallCourtesyPresenter(Service, story, log); } public void OnLoadComplete() { if (!_disposed) { _reportedAmbiguities.Clear(); Presenter.ClearFeedback(); Service.OnLoadComplete(); ReportAmbiguities(); } } public void OnPreLoad() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnPreLoad(); _reportedAmbiguities.Clear(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); ReportAmbiguities(); } } public void Update() { if (!_disposed) { Service.Update(); ReportAmbiguities(); } } public void Dispose() { if (!_disposed) { _disposed = true; Presenter.Dispose(); Service.Dispose(); if (_world is IDisposable disposable) { disposable.Dispose(); } _reportedAmbiguities.Clear(); } } private void ReportAmbiguities() { Release1StoryState state = _story.State; if ((object)state == null) { return; } foreach (Release1NativeEffectJournalEntry item in state.NativeEffects.Where((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.small-courtesy" && effect.ExecutionBlocked)) { if (_reportedAmbiguities.Add(item.EffectId)) { _log?.Invoke("Small Courtesy native effect stopped as ambiguous: " + item.EffectId + "; no automatic retry will occur."); } } } } public enum Release1SmallCourtesyWorldReadStatus { Ready, Pending, NotAuthoritative, Unavailable, Faulted } public enum Release1SmallCourtesyPackageKind { Brick, Jar } public sealed record Release1SmallCourtesyPackagingCandidate(string PackagingId, string PackagingName) { public void Validate() { Release1SmallCourtesyAssignment.ValidateStableId(PackagingId, "PackagingId"); Release1SmallCourtesyAssignment.ValidatePresentation(PackagingName, 256, "PackagingName"); } } public interface IRelease1SmallCourtesyWorld { Release1SmallCourtesyWorldReadStatus TryReadContext(out Release1StoryHostContextSnapshot context); Release1SmallCourtesyWorldReadStatus TryReadCanonicalTotalMinutes(out double totalMinutes); Release1SmallCourtesyWorldReadStatus TryReadProducts(out IReadOnlyList products); Release1SmallCourtesyWorldReadStatus TryReadDeadDrops(out IReadOnlyList drops); Release1SmallCourtesyWorldReadStatus TryReadPackaging(Release1SmallCourtesyPackageKind kind, out Release1SmallCourtesyPackagingCandidate packaging); Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlots(string deadDropGuid, out IReadOnlyList slots); Release1SmallCourtesyWorldReadStatus TryReadHoldRoom(out Release1HoldRoomSnapshot room); Release1SmallCourtesyWorldMutationStatus TrySetSlotLocked(string deadDropGuid, int slotIndex, bool locked); Release1SmallCourtesyWorldMutationStatus TryChangeSlotQuantity(string deadDropGuid, int slotIndex, int amount); Release1SmallCourtesyWorldMutationStatus TryInsertPackagedProduct(string deadDropGuid, int slotIndex, string productId, string packagingId, int quantity, out string reason); Release1SmallCourtesyWorldReadStatus TrySubscribeDeadDropClosed(string deadDropGuid, Action callback, out IRelease1SmallCourtesyDropSubscription? subscription); Release1SmallCourtesyWorldReadStatus TryReadCashBalance(out float balance); Release1SmallCourtesyWorldMutationStatus TryChangeCashBalance(float amount); Release1SmallCourtesyWorldMutationStatus TryDebitCashBalance(float amount); Release1SmallCourtesyWorldMutationStatus TryEngageLockdown(out string reason); Release1SmallCourtesyWorldMutationStatus TryReleaseLockdown(out string reason); Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, out float balance); Release1SmallCourtesyWorldMutationStatus TryChangeDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, float amount); Release1SmallCourtesyWorldReadStatus TryReadHoldRoomSlotCashBalance(string closetGuid, int slotIndex, out float balance); Release1SmallCourtesyWorldMutationStatus TrySetHoldRoomSlotLocked(string closetGuid, int slotIndex, bool locked); Release1SmallCourtesyWorldMutationStatus TryChangeHoldRoomSlotCashBalance(string closetGuid, int slotIndex, float amount); Release1SmallCourtesyWorldReadStatus TryReadProductionActivity(out Release1ProductionActivitySnapshot activity); Release1SmallCourtesyWorldMutationStatus TryParkFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldMutationStatus TryUnparkFieldContact(string contactId, float aheadMetres, out string reason); Release1SmallCourtesyWorldMutationStatus TryDespawnFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldMutationStatus TryProvokeFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldReadStatus TryReadFieldContact(string contactId, out Release1FieldContactSnapshot snapshot); } public enum Release1SmallCourtesyWorldMutationStatus { Succeeded, Rejected, Unavailable, Ambiguous } public sealed record Release1SmallCourtesySlotSnapshot(int SlotIndex, string? ProductId, string? PackagingId, int Quantity, bool IsPackaged, float MonetaryValue) { public void Validate() { if (SlotIndex < 0 || Quantity < 0 || !float.IsFinite(MonetaryValue) || MonetaryValue < 0f) { throw new ArgumentException("Slot snapshot values were invalid."); } if (Quantity != 0) { Release1SmallCourtesyAssignment.ValidateStableId(ProductId, "ProductId"); Release1SmallCourtesyAssignment.ValidateStableId(PackagingId, "PackagingId"); } } } public interface IRelease1SmallCourtesyDropSubscription : IDisposable { string DeadDropGuid { get; } } public enum Release1SmallCourtesyDepositStatus { NoWork, AwaitingPreparedSave, Applied, AwaitingAppliedSave, Committed, Ambiguous, Rejected } public enum Release1SmallCourtesyRewardStatus { NoWork, AwaitingPreparedSave, Applied, AwaitingAppliedSave, Committed, Ambiguous, Rejected } public enum Release1SmallCourtesyOfferStatus { Inactive, Available, Ineligible, Pending, Saving, NoDiscoveredProduct, NoEmptyDeadDrop, Unavailable, Dismissed, Disposed } public enum Release1SmallCourtesyReviewStatus { Ready, Inactive, Ineligible, Pending, Saving, NoDiscoveredProduct, NoEmptyDeadDrop, Unavailable, Dismissed, Disposed } public enum Release1SmallCourtesyDecisionStatus { Accepted, Deferred, Dismissed, ReviewRequired, QuoteChanged, PersistenceDeferred, Rejected, Inactive, Disposed } public sealed record Release1SmallCourtesyQuote(Release1SmallCourtesyAssignment Assignment, double? DeadlineDurationHours) { public void Validate() { Assignment.Validate(); if (Assignment.Mode == Release1SmallCourtesyAssignmentMode.Recovery) { if (DeadlineDurationHours.HasValue) { throw new ArgumentException("Recovery quotes cannot have a deadline.", "DeadlineDurationHours"); } } else if (DeadlineDurationHours.GetValueOrDefault() != 24.0) { throw new ArgumentException("Timed Small Courtesy stages require a 24-hour duration.", "DeadlineDurationHours"); } } } public sealed record Release1SmallCourtesyReviewResult(Release1SmallCourtesyReviewStatus Status, Release1SmallCourtesyQuote? Quote, string Message); public sealed record Release1SmallCourtesyDecisionResult(Release1SmallCourtesyDecisionStatus Status, string Message); public sealed class Release1SmallCourtesyMissionService : IDisposable { private const string TermsVersion = "small-courtesy-v1"; private const double TimedStageDurationHours = 24.0; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private bool _loadActive; private bool _saving; private bool _dismissed; private bool _disposed; private Release1SmallCourtesyQuote? _reviewedQuote; private IRelease1SmallCourtesyDropSubscription? _dropSubscription; private string? _boundDropGuid; private string? _lockedDropGuid; private int _lockedSlotIndex = -1; public Release1SmallCourtesyOfferStatus OfferStatus { get; private set; } public Release1SmallCourtesyQuote? ReviewedQuote => _reviewedQuote; public Release1SmallCourtesyMissionService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); } public void OnLoadComplete() { if (!_disposed && !_loadActive) { _loadActive = true; _saving = false; _dismissed = false; _reviewedQuote = null; RefreshOfferStatus(); RefreshDepositBinding(); ReconcileDeposit(); ReconcileReward(); } } public void OnPreLoad() { if (!_disposed) { ReleaseSlotLock(); DisposeDropSubscription(); _loadActive = false; _saving = false; _dismissed = false; _reviewedQuote = null; OfferStatus = Release1SmallCourtesyOfferStatus.Inactive; } } public void OnSaveStart() { if (!_disposed && _loadActive) { _saving = true; OfferStatus = Release1SmallCourtesyOfferStatus.Saving; } } public void OnSaveComplete() { if (!_disposed && _loadActive) { _saving = false; RefreshOfferStatus(); RefreshDepositBinding(); ReconcileDeposit(); ReconcileReward(); } } public void Update() { if (_disposed || !_loadActive || _saving) { return; } Release1StoryHostContextSnapshot context; Release1SmallCourtesyReviewStatus release1SmallCourtesyReviewStatus = ReadMatchingContext(out context); if (release1SmallCourtesyReviewStatus != Release1SmallCourtesyReviewStatus.Ready) { OfferStatus = Map(release1SmallCourtesyReviewStatus); return; } Release1StoryState state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Accepted) { Release1MissionRecord release1MissionRecord = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive) ? true : false); if (!flag || !release1MissionRecord.DeadlineGameTimeHours.HasValue) { RefreshOfferStatus(); RefreshDepositBinding(); ReconcileDeposit(); ReconcileReward(); return; } if (!TryReadGameHours(out var gameHours) || gameHours < release1MissionRecord.DeadlineGameTimeHours.Value) { RefreshDepositBinding(); ReconcileDeposit(); ReconcileReward(); return; } if (HasCargoEffect(release1MissionRecord.Attempt)) { RefreshDepositBinding(); ReconcileDeposit(); ReconcileReward(); return; } Release1TransitionKind transition = ((release1MissionRecord.State == Release1MissionState.Active) ? Release1TransitionKind.RequiredFailure : Release1TransitionKind.MakeGoodFailed); string receipt = $"small-courtesy-deadline-{ModeToken(release1MissionRecord.State)}-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = CreateCommand(transition, release1MissionRecord.Attempt, receipt, null, null, null); _story.TryExecuteDurably(command); RefreshOfferStatus(); RefreshDepositBinding(); ReconcileDeposit(); ReconcileReward(); } } public Release1SmallCourtesyReviewResult TryReview() { if (_disposed) { return Review(Release1SmallCourtesyReviewStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Review(Release1SmallCourtesyReviewStatus.Inactive, "No load epoch is active."); } if (_saving) { return Review(Release1SmallCourtesyReviewStatus.Saving, "Mission review is deferred during saving."); } if (_dismissed) { return Review(Release1SmallCourtesyReviewStatus.Dismissed, "Offer was dismissed for this load."); } Release1SmallCourtesyQuote quote; Release1SmallCourtesyReviewStatus release1SmallCourtesyReviewStatus = TryBuildQuote(out quote); if (release1SmallCourtesyReviewStatus != Release1SmallCourtesyReviewStatus.Ready) { _reviewedQuote = null; OfferStatus = Map(release1SmallCourtesyReviewStatus); return Review(release1SmallCourtesyReviewStatus, Message(release1SmallCourtesyReviewStatus)); } _reviewedQuote = quote; OfferStatus = Release1SmallCourtesyOfferStatus.Available; return new Release1SmallCourtesyReviewResult(Release1SmallCourtesyReviewStatus.Ready, quote, "Small Courtesy terms are ready for review."); } public Release1SmallCourtesyDecisionResult TryAccept() { if (_disposed) { return Decision(Release1SmallCourtesyDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _dismissed) { return Decision(Release1SmallCourtesyDecisionStatus.Inactive, "No active offer is available."); } if (_saving) { return Decision(Release1SmallCourtesyDecisionStatus.PersistenceDeferred, "Acceptance is deferred during saving."); } if ((object)_reviewedQuote == null) { return Decision(Release1SmallCourtesyDecisionStatus.ReviewRequired, "Review the current terms before accepting."); } if (TryBuildQuote(out Release1SmallCourtesyQuote quote) != Release1SmallCourtesyReviewStatus.Ready || quote != _reviewedQuote) { _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1SmallCourtesyDecisionStatus.QuoteChanged, "The available product, package, or drop changed; review again."); } if (!TryReadGameHours(out var gameHours)) { return Decision(Release1SmallCourtesyDecisionStatus.Rejected, "Canonical game time was unavailable."); } Release1SmallCourtesyAssignment assignment = quote.Assignment; Release1TransitionKind transition = assignment.Mode switch { Release1SmallCourtesyAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1SmallCourtesyAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1SmallCourtesyAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string receipt = AcceptanceReceipt(assignment.Mode, assignment.Attempt); Release1StoryCommand command = CreateCommand(transition, assignment.Attempt, receipt, (assignment.Mode == Release1SmallCourtesyAssignmentMode.Primary) ? "small-courtesy-v1" : null, gameHours, (assignment.Mode == Release1SmallCourtesyAssignmentMode.Recovery) ? ((double?)null) : new double?(gameHours + 24.0)); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteSmallCourtesyAcceptanceDurably(command, assignment); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1SmallCourtesyDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1SmallCourtesyDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } _reviewedQuote = null; RefreshOfferStatus(); RefreshDepositBinding(); return Decision(Release1SmallCourtesyDecisionStatus.Accepted, "Small Courtesy stage was accepted."); } public Release1SmallCourtesyDecisionResult TryDefer() { if (_disposed) { return Decision(Release1SmallCourtesyDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _saving || ReadMatchingContext(out var _) != Release1SmallCourtesyReviewStatus.Ready) { return Decision(Release1SmallCourtesyDecisionStatus.Inactive, "No active offer is available."); } Release1MissionRecord release1MissionRecord = _story.State?.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1StoryState? state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted || (object)release1MissionRecord == null || release1MissionRecord.State != Release1MissionState.Offered) { return TryDismiss(); } string receipt = $"small-courtesy-defer-v1-a{release1MissionRecord.Attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(Release1TransitionKind.MissionDeferred, release1MissionRecord.Attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1SmallCourtesyDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1SmallCourtesyDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } DismissForLoad(); return Decision(Release1SmallCourtesyDecisionStatus.Deferred, "Small Courtesy was deferred until a later load."); } public Release1SmallCourtesyDecisionResult TryDismiss() { if (_disposed) { return Decision(Release1SmallCourtesyDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Decision(Release1SmallCourtesyDecisionStatus.Inactive, "No load epoch is active."); } DismissForLoad(); return Decision(Release1SmallCourtesyDecisionStatus.Dismissed, "Small Courtesy was dismissed for this load."); } public Release1SmallCourtesyDepositStatus TryHandleDropClosed(string deadDropGuid) { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1SmallCourtesyReviewStatus.Ready) { return Release1SmallCourtesyDepositStatus.Rejected; } if (!TryGetCurrentDeposit(out Release1MissionRecord mission, out Release1SmallCourtesyAssignment assignment, out string authorization)) { return Release1SmallCourtesyDepositStatus.NoWork; } if (!string.Equals(deadDropGuid, assignment.DeadDropGuid, StringComparison.Ordinal)) { return Release1SmallCourtesyDepositStatus.NoWork; } Release1MissionState state = mission.State; bool flag = ((state == Release1MissionState.Active || state == Release1MissionState.MakeGoodActive) ? true : false); if (flag && (!mission.DeadlineGameTimeHours.HasValue || !TryReadGameHours(out var gameHours) || gameHours >= mission.DeadlineGameTimeHours.Value)) { return Release1SmallCourtesyDepositStatus.NoWork; } string effectId = CargoEffectId(mission.Attempt); if (_story.State.NativeEffects.Any((Release1NativeEffectJournalEntry release1NativeEffectJournalEntry) => release1NativeEffectJournalEntry.EffectId == effectId)) { return ReconcileDeposit(); } if (!TryReadSlots(assignment.DeadDropGuid, out IReadOnlyList slots)) { return Release1SmallCourtesyDepositStatus.Rejected; } Release1SmallCourtesySlotSnapshot release1SmallCourtesySlotSnapshot = (from candidate in slots where candidate.Quantity > 0 && candidate.IsPackaged && string.Equals(candidate.ProductId, assignment.ProductId, StringComparison.Ordinal) && string.Equals(candidate.PackagingId, assignment.PackagingId, StringComparison.Ordinal) orderby candidate.SlotIndex select candidate).FirstOrDefault(); if ((object)release1SmallCourtesySlotSnapshot == null) { return Release1SmallCourtesyDepositStatus.NoWork; } bool flag2 = false; try { Release1SmallCourtesyCargoIdentity release1SmallCourtesyCargoIdentity = new Release1SmallCourtesyCargoIdentity(assignment.ProductId, assignment.PackagingId, release1SmallCourtesySlotSnapshot.SlotIndex, release1SmallCourtesySlotSnapshot.Quantity, release1SmallCourtesySlotSnapshot.Quantity - 1, release1SmallCourtesySlotSnapshot.MonetaryValue); Release1NativeEffectJournalEntry effect = new Release1NativeEffectJournalEntry(effectId, "release1.small-courtesy", mission.Attempt, "CargoTransfer", assignment.DeadDropGuid, "oc-small-courtesy", release1SmallCourtesyCargoIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, _story.State.Revision + 1, null, authorization, ExecutionBlocked: false, mission.Revision); if (!TryAcquireSlotLock(assignment.DeadDropGuid, release1SmallCourtesySlotSnapshot.SlotIndex)) { return Release1SmallCourtesyDepositStatus.Rejected; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryPrepareNativeEffect(effect); flag2 = release1StoryRuntimeCommandResult.Accepted; return release1StoryRuntimeCommandResult.Accepted ? Release1SmallCourtesyDepositStatus.AwaitingPreparedSave : Release1SmallCourtesyDepositStatus.Rejected; } catch (ArgumentException) { return Release1SmallCourtesyDepositStatus.Rejected; } finally { if (!flag2) { ReleaseSlotLock(); } } } public Release1SmallCourtesyDepositStatus ReconcileDeposit() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1SmallCourtesyReviewStatus.Ready) { return Release1SmallCourtesyDepositStatus.NoWork; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1SmallCourtesyDepositStatus.NoWork; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == CargoEffectId(mission.Attempt)); if ((object)release1NativeEffectJournalEntry == null) { return Release1SmallCourtesyDepositStatus.NoWork; } Release1SmallCourtesyAssignment release1SmallCourtesyAssignment = state.SmallCourtesyAssignments.SingleOrDefault((Release1SmallCourtesyAssignment candidate) => candidate.Attempt == mission.Attempt); string authorizedStoryCorrelationId = release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId; if ((object)release1SmallCourtesyAssignment == null || string.IsNullOrEmpty(authorizedStoryCorrelationId)) { return BlockPreparedEffect(release1NativeEffectJournalEntry); } if (!Release1SmallCourtesyCargoIdentity.TryParse(release1NativeEffectJournalEntry.AmountOrCargoIdentity, out Release1SmallCourtesyCargoIdentity identity) || (object)identity == null || release1NativeEffectJournalEntry.Attempt != mission.Attempt || !string.Equals(release1NativeEffectJournalEntry.EffectKind, "CargoTransfer", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.SourceIdentity, release1SmallCourtesyAssignment.DeadDropGuid, StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.DestinationIdentity, "oc-small-courtesy", StringComparison.Ordinal) || !string.Equals(identity.ProductId, release1SmallCourtesyAssignment.ProductId, StringComparison.Ordinal) || !string.Equals(identity.PackageId, release1SmallCourtesyAssignment.PackagingId, StringComparison.Ordinal)) { return BlockPreparedEffect(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.ExecutionBlocked) { return Release1SmallCourtesyDepositStatus.Ambiguous; } if (!TryReadSlot(release1SmallCourtesyAssignment.DeadDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot)) { return Release1SmallCourtesyDepositStatus.Rejected; } bool flag = MatchesCargoSlot(slot, identity, identity.PreQuantity); bool flag2 = MatchesCargoSlot(slot, identity, identity.PostQuantity); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { if (release1NativeEffectJournalEntry.PreparedStoryRevision > _story.LastPersistedRevision) { return Release1SmallCourtesyDepositStatus.AwaitingPreparedSave; } if (flag2) { Release1SmallCourtesyDepositStatus result = MarkCargoApplied(release1NativeEffectJournalEntry, authorizedStoryCorrelationId); ReleaseSlotLock(); return result; } if (!flag) { return BlockPreparedEffect(release1NativeEffectJournalEntry); } return ApplyPreparedCargo(release1NativeEffectJournalEntry, release1SmallCourtesyAssignment, identity, authorizedStoryCorrelationId); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied) { if (!flag2) { return Release1SmallCourtesyDepositStatus.Ambiguous; } if (!TryCompleteMissionAndPrepareReward(release1NativeEffectJournalEntry, identity)) { return Release1SmallCourtesyDepositStatus.Rejected; } if (_story.State.Revision > _story.LastPersistedRevision) { return Release1SmallCourtesyDepositStatus.AwaitingAppliedSave; } if (!_story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorizedStoryCorrelationId).Accepted) { return Release1SmallCourtesyDepositStatus.Rejected; } return Release1SmallCourtesyDepositStatus.Committed; } return Release1SmallCourtesyDepositStatus.Committed; } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; OfferStatus = Release1SmallCourtesyOfferStatus.Disposed; } } private Release1SmallCourtesyDepositStatus ApplyPreparedCargo(Release1NativeEffectJournalEntry effect, Release1SmallCourtesyAssignment assignment, Release1SmallCourtesyCargoIdentity identity, string authorization) { if (!TryAcquireSlotLock(assignment.DeadDropGuid, identity.SlotIndex)) { return Release1SmallCourtesyDepositStatus.Rejected; } try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeSlotQuantity(assignment.DeadDropGuid, identity.SlotIndex, -1); } catch { return BlockPreparedEffect(effect); } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return (release1SmallCourtesyWorldMutationStatus == Release1SmallCourtesyWorldMutationStatus.Unavailable) ? Release1SmallCourtesyDepositStatus.Rejected : BlockPreparedEffect(effect); } if (!TryReadSlot(assignment.DeadDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot) || !MatchesCargoSlot(slot, identity, identity.PostQuantity)) { return BlockPreparedEffect(effect); } return MarkCargoApplied(effect, authorization); } finally { ReleaseSlotLock(); } } private Release1SmallCourtesyDepositStatus MarkCargoApplied(Release1NativeEffectJournalEntry effect, string authorization) { if (!string.Equals(effect.AuthorizedStoryCorrelationId, authorization, StringComparison.Ordinal)) { return BlockPreparedEffect(effect); } string nativeReceiptId = $"small-courtesy-cargo-native-v1-a{effect.Attempt}"; if (!_story.TryMarkNativeEffectApplied(effect.EffectId, nativeReceiptId).Accepted) { return Release1SmallCourtesyDepositStatus.Rejected; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effect.EffectId); if (Release1SmallCourtesyCargoIdentity.TryParse(release1NativeEffectJournalEntry.AmountOrCargoIdentity, out Release1SmallCourtesyCargoIdentity identity) && (object)identity != null) { TryCompleteMissionAndPrepareReward(release1NativeEffectJournalEntry, identity); } return Release1SmallCourtesyDepositStatus.Applied; } public Release1SmallCourtesyRewardStatus ReconcileReward() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1SmallCourtesyReviewStatus.Ready) { return Release1SmallCourtesyRewardStatus.NoWork; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1SmallCourtesyRewardStatus.NoWork; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == CargoEffectId(mission.Attempt)); if ((object)release1NativeEffectJournalEntry == null || release1NativeEffectJournalEntry.ExecutionBlocked || release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Committed) { return Release1SmallCourtesyRewardStatus.NoWork; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == RewardEffectId(mission.Attempt)); if ((object)release1NativeEffectJournalEntry2 == null) { return Release1SmallCourtesyRewardStatus.NoWork; } if (!Release1SmallCourtesyRewardIdentity.TryParse(release1NativeEffectJournalEntry2.AmountOrCargoIdentity, out Release1SmallCourtesyRewardIdentity identity) || (object)identity == null || release1NativeEffectJournalEntry2.Attempt != mission.Attempt || !string.Equals(release1NativeEffectJournalEntry2.EffectKind, "Reward", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry2.SourceIdentity, "oc-small-courtesy", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry2.DestinationIdentity, "player-cash", StringComparison.Ordinal)) { return BlockRewardEffect(release1NativeEffectJournalEntry2); } if (release1NativeEffectJournalEntry2.ExecutionBlocked) { return Release1SmallCourtesyRewardStatus.Ambiguous; } if (!TryReadCashBalance(out var balance)) { return Release1SmallCourtesyRewardStatus.Rejected; } bool flag = WithinCashTolerance(balance, identity.BaselineCash, identity.VerificationTolerance); bool flag2 = WithinCashTolerance(balance, identity.ExpectedCash, identity.VerificationTolerance); if (release1NativeEffectJournalEntry2.Phase == Release1NativeEffectPhase.Prepared) { if (release1NativeEffectJournalEntry2.PreparedStoryRevision > _story.LastPersistedRevision) { return Release1SmallCourtesyRewardStatus.AwaitingPreparedSave; } if (flag2) { return MarkRewardApplied(release1NativeEffectJournalEntry2); } if (!flag) { return BlockRewardEffect(release1NativeEffectJournalEntry2); } return ApplyPreparedReward(release1NativeEffectJournalEntry2, identity); } if (release1NativeEffectJournalEntry2.Phase == Release1NativeEffectPhase.Applied) { if (_story.State.Revision > _story.LastPersistedRevision) { return Release1SmallCourtesyRewardStatus.AwaitingAppliedSave; } if (!flag2) { return Release1SmallCourtesyRewardStatus.Ambiguous; } string authorizedStoryCorrelationId = release1NativeEffectJournalEntry2.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId)) { return Release1SmallCourtesyRewardStatus.Ambiguous; } if (!_story.TryCommitNativeEffect(release1NativeEffectJournalEntry2.EffectId, authorizedStoryCorrelationId).Accepted) { return Release1SmallCourtesyRewardStatus.Rejected; } return Release1SmallCourtesyRewardStatus.Committed; } return Release1SmallCourtesyRewardStatus.Committed; } private bool TryCompleteMissionAndPrepareReward(Release1NativeEffectJournalEntry cargoEffect, Release1SmallCourtesyCargoIdentity cargoIdentity) { Release1StoryState state = _story.State; if ((object)state == null) { return false; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry effect) => effect.EffectId == RewardEffectId(mission.Attempt)); if (mission.State == Release1MissionState.Satisfied) { return (object)release1NativeEffectJournalEntry != null; } Release1MissionState state2 = mission.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive || state2 == Release1MissionState.RecoveryActive) ? true : false); if (!flag || cargoEffect.Phase != Release1NativeEffectPhase.Applied || cargoEffect.ExecutionBlocked || cargoEffect.Attempt != mission.Attempt || (object)release1NativeEffectJournalEntry != null || !TryReadCashBalance(out var balance)) { return false; } float? num = Release1RewardMath.QuoteWholeDollars(cargoIdentity.DepositedMonetaryValue); if (num.HasValue) { float valueOrDefault = num.GetValueOrDefault(); Release1SmallCourtesyRewardIdentity release1SmallCourtesyRewardIdentity; try { release1SmallCourtesyRewardIdentity = new Release1SmallCourtesyRewardIdentity(balance, valueOrDefault, balance + valueOrDefault); } catch (ArgumentException) { return false; } if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return false; } string receiptId = $"small-courtesy-complete-v1-a{mission.Attempt}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.small-courtesy", mission.Attempt, Release1TransitionKind.MissionCompleted, receiptId).Value; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = new Release1NativeEffectJournalEntry(RewardEffectId(mission.Attempt), "release1.small-courtesy", mission.Attempt, "Reward", "oc-small-courtesy", "player-cash", release1SmallCourtesyRewardIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, state.Revision + 1, null, value, ExecutionBlocked: false, mission.Revision); Guid sessionEpoch = snapshot.SessionEpoch; long loadEpoch = snapshot.LoadEpoch; string playerId = snapshot.PlayerId; int attempt = mission.Attempt; Release1CompletionTiming? completionTiming = ((mission.State != Release1MissionState.Active) ? Release1CompletionTiming.Late : Release1CompletionTiming.OnTime); string rewardAuthorizationReceiptId = $"small-courtesy-reward-auth-v1-a{mission.Attempt}"; Release1NativeEffectJournalEntry preparedNativeEffect = release1NativeEffectJournalEntry2; Release1StoryCommand command = new Release1StoryCommand(sessionEpoch, loadEpoch, playerId, "release1.small-courtesy", attempt, Release1TransitionKind.MissionCompleted, receiptId, value, null, null, null, completionTiming, rewardAuthorizationReceiptId, null, null, preparedNativeEffect); return _story.TryExecute(command).Accepted; } return false; } private Release1SmallCourtesyRewardStatus ApplyPreparedReward(Release1NativeEffectJournalEntry effect, Release1SmallCourtesyRewardIdentity identity) { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeCashBalance(identity.WholeDollarAmount); } catch { return BlockRewardEffect(effect); } switch (release1SmallCourtesyWorldMutationStatus) { default: return BlockRewardEffect(effect); case Release1SmallCourtesyWorldMutationStatus.Unavailable: return Release1SmallCourtesyRewardStatus.Rejected; case Release1SmallCourtesyWorldMutationStatus.Succeeded: { if (!TryReadCashBalance(out var balance) || !WithinCashTolerance(balance, identity.ExpectedCash, identity.VerificationTolerance)) { return BlockRewardEffect(effect); } return MarkRewardApplied(effect); } } } private Release1SmallCourtesyRewardStatus MarkRewardApplied(Release1NativeEffectJournalEntry effect) { string nativeReceiptId = $"small-courtesy-reward-native-v1-a{effect.Attempt}"; if (!_story.TryMarkNativeEffectApplied(effect.EffectId, nativeReceiptId).Accepted) { return Release1SmallCourtesyRewardStatus.Rejected; } return Release1SmallCourtesyRewardStatus.Applied; } private Release1SmallCourtesyRewardStatus BlockRewardEffect(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } return Release1SmallCourtesyRewardStatus.Ambiguous; } private bool TryReadCashBalance(out float balance) { balance = 0f; try { return _world.TryReadCashBalance(out balance) == Release1SmallCourtesyWorldReadStatus.Ready && float.IsFinite(balance) && balance >= 0f; } catch { return false; } } private static bool WithinCashTolerance(float actual, float expected, float tolerance) { if (float.IsFinite(actual)) { return MathF.Abs(actual - expected) <= tolerance; } return false; } private Release1SmallCourtesyDepositStatus BlockPreparedEffect(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } ReleaseSlotLock(); return Release1SmallCourtesyDepositStatus.Ambiguous; } private void RefreshDepositBinding() { if (!_loadActive || _disposed || !TryGetCurrentDeposit(out Release1MissionRecord _, out Release1SmallCourtesyAssignment assignment, out string _)) { DisposeDropSubscription(); } else { if (_dropSubscription != null && string.Equals(_boundDropGuid, assignment.DeadDropGuid, StringComparison.Ordinal)) { return; } DisposeDropSubscription(); try { if (_world.TrySubscribeDeadDropClosed(assignment.DeadDropGuid, TryHandleDropClosedCallback, out IRelease1SmallCourtesyDropSubscription subscription) != Release1SmallCourtesyWorldReadStatus.Ready || subscription == null || !string.Equals(subscription.DeadDropGuid, assignment.DeadDropGuid, StringComparison.Ordinal)) { subscription?.Dispose(); return; } _dropSubscription = subscription; _boundDropGuid = assignment.DeadDropGuid; } catch { DisposeDropSubscription(); } } } private void TryHandleDropClosedCallback(string deadDropGuid) { TryHandleDropClosed(deadDropGuid); } private bool TryGetCurrentDeposit(out Release1MissionRecord mission, out Release1SmallCourtesyAssignment assignment, out string authorization) { mission = null; assignment = null; authorization = string.Empty; Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return false; } mission = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1SmallCourtesyAssignmentMode? expectedMode = mission.State switch { Release1MissionState.Active => Release1SmallCourtesyAssignmentMode.Primary, Release1MissionState.MakeGoodActive => Release1SmallCourtesyAssignmentMode.MakeGood, Release1MissionState.RecoveryActive => Release1SmallCourtesyAssignmentMode.Recovery, _ => null, }; if (!expectedMode.HasValue) { return false; } int missionAttempt = mission.Attempt; assignment = state.SmallCourtesyAssignments.SingleOrDefault((Release1SmallCourtesyAssignment candidate) => candidate.Attempt == missionAttempt && candidate.Mode == expectedMode.Value); if ((object)assignment == null) { return false; } if (expectedMode == Release1SmallCourtesyAssignmentMode.Primary) { authorization = mission.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) && correlation.TransitionKind == Release1TransitionKind.MissionActivated) ?? string.Empty; } else { authorization = assignment.AuthorizationCorrelationId; } return !string.IsNullOrEmpty(authorization); } private bool TryReadSlots(string deadDropGuid, out IReadOnlyList slots) { slots = Array.Empty(); try { if (_world.TryReadDeadDropSlots(deadDropGuid, out IReadOnlyList slots2) != Release1SmallCourtesyWorldReadStatus.Ready || slots2 == null) { return false; } Release1SmallCourtesySlotSnapshot[] array = slots2.ToArray(); Release1SmallCourtesySlotSnapshot[] array2 = array; for (int i = 0; i < array2.Length; i++) { array2[i].Validate(); } if (array.Select((Release1SmallCourtesySlotSnapshot slot) => slot.SlotIndex).Distinct().Count() != array.Length) { return false; } slots = array; return true; } catch { return false; } } private bool TryReadSlot(string deadDropGuid, int slotIndex, out Release1SmallCourtesySlotSnapshot slot) { slot = null; if (!TryReadSlots(deadDropGuid, out IReadOnlyList slots)) { return false; } slot = slots.SingleOrDefault((Release1SmallCourtesySlotSnapshot candidate) => candidate.SlotIndex == slotIndex); return (object)slot != null; } private bool TryAcquireSlotLock(string deadDropGuid, int slotIndex) { if (_lockedDropGuid != null) { if (string.Equals(_lockedDropGuid, deadDropGuid, StringComparison.Ordinal)) { return _lockedSlotIndex == slotIndex; } return false; } _lockedDropGuid = deadDropGuid; _lockedSlotIndex = slotIndex; try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = _world.TrySetSlotLocked(deadDropGuid, slotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { if (release1SmallCourtesyWorldMutationStatus == Release1SmallCourtesyWorldMutationStatus.Ambiguous) { ReleaseSlotLock(); } else { _lockedDropGuid = null; _lockedSlotIndex = -1; } return false; } return true; } catch { ReleaseSlotLock(); return false; } } private void ReleaseSlotLock() { if (_lockedDropGuid == null || _lockedSlotIndex < 0) { return; } try { if (_world.TrySetSlotLocked(_lockedDropGuid, _lockedSlotIndex, locked: false) == Release1SmallCourtesyWorldMutationStatus.Succeeded) { _lockedDropGuid = null; _lockedSlotIndex = -1; } } catch { } } private void DisposeDropSubscription() { try { _dropSubscription?.Dispose(); } catch { } _dropSubscription = null; _boundDropGuid = null; } private static bool MatchesCargoSlot(Release1SmallCourtesySlotSnapshot slot, Release1SmallCourtesyCargoIdentity identity, int expectedQuantity) { if (slot.SlotIndex != identity.SlotIndex || slot.Quantity != expectedQuantity) { return false; } if (expectedQuantity == 0) { return true; } if (slot.IsPackaged && string.Equals(slot.ProductId, identity.ProductId, StringComparison.Ordinal) && string.Equals(slot.PackagingId, identity.PackageId, StringComparison.Ordinal)) { return slot.MonetaryValue == identity.DepositedMonetaryValue; } return false; } private static string CargoEffectId(int attempt) { return $"small-courtesy-cargo-v1-a{attempt}"; } private bool HasCargoEffect(int attempt) { return _story.State?.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.EffectId == CargoEffectId(attempt)) ?? false; } private static string RewardEffectId(int attempt) { return $"small-courtesy-reward-v1-a{attempt}"; } private Release1SmallCourtesyReviewStatus TryBuildQuote(out Release1SmallCourtesyQuote? quote) { try { return TryBuildQuoteCore(out quote); } catch { quote = null; return Release1SmallCourtesyReviewStatus.Unavailable; } } private Release1SmallCourtesyReviewStatus TryBuildQuoteCore(out Release1SmallCourtesyQuote? quote) { quote = null; Release1StoryHostContextSnapshot context; Release1SmallCourtesyReviewStatus release1SmallCourtesyReviewStatus = ReadMatchingContext(out context); if (release1SmallCourtesyReviewStatus != Release1SmallCourtesyReviewStatus.Ready) { return release1SmallCourtesyReviewStatus; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1SmallCourtesyReviewStatus.Ineligible; } Release1MissionRecord release1MissionRecord = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1SmallCourtesyAssignmentMode? release1SmallCourtesyAssignmentMode = release1MissionRecord.State switch { Release1MissionState.Offered => Release1SmallCourtesyAssignmentMode.Primary, Release1MissionState.MakeGoodOffered => Release1SmallCourtesyAssignmentMode.MakeGood, Release1MissionState.RecoveryAvailable => Release1SmallCourtesyAssignmentMode.Recovery, _ => null, }; if (!release1SmallCourtesyAssignmentMode.HasValue) { return Release1SmallCourtesyReviewStatus.Ineligible; } int attempt = ((release1SmallCourtesyAssignmentMode == Release1SmallCourtesyAssignmentMode.Primary) ? release1MissionRecord.Attempt : (release1MissionRecord.Attempt + 1)); Release1TransitionKind transitionKind = release1SmallCourtesyAssignmentMode switch { Release1SmallCourtesyAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1SmallCourtesyAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1SmallCourtesyAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string receiptId = AcceptanceReceipt(release1SmallCourtesyAssignmentMode.Value, attempt); string value = Release1LogicalCorrelation.Create(context.PlayerId, "release1.small-courtesy", attempt, transitionKind, receiptId).Value; Release1SmallCourtesyPackageKind kind = ((release1SmallCourtesyAssignmentMode != Release1SmallCourtesyAssignmentMode.Primary) ? Release1SmallCourtesyPackageKind.Jar : Release1SmallCourtesyPackageKind.Brick); if (_world.TryReadPackaging(kind, out Release1SmallCourtesyPackagingCandidate packaging) != Release1SmallCourtesyWorldReadStatus.Ready) { return Release1SmallCourtesyReviewStatus.Unavailable; } try { packaging.Validate(); } catch (ArgumentException) { return Release1SmallCourtesyReviewStatus.Unavailable; } IReadOnlyList products; if (release1SmallCourtesyAssignmentMode == Release1SmallCourtesyAssignmentMode.Primary) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadProducts(out products); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } } else { Release1SmallCourtesyAssignment release1SmallCourtesyAssignment = state.SmallCourtesyAssignments.OrderBy((Release1SmallCourtesyAssignment candidate) => candidate.Attempt).FirstOrDefault(); if ((object)release1SmallCourtesyAssignment == null) { return Release1SmallCourtesyReviewStatus.Ineligible; } products = new Release1SmallCourtesyProductCandidate[1] { new Release1SmallCourtesyProductCandidate(release1SmallCourtesyAssignment.ProductId, release1SmallCourtesyAssignment.ProductName, release1SmallCourtesyAssignment.SelectionAskingPrice, IsDiscovered: true) }; } IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = _world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus2); } if (!Release1SmallCourtesyAssignmentSelector.TrySelect(products, drops, release1SmallCourtesyAssignmentMode.Value, attempt, value, packaging.PackagingId, packaging.PackagingName, out Release1SmallCourtesyAssignment assignment, out Release1SmallCourtesyAssignmentSelectionStatus status)) { return status switch { Release1SmallCourtesyAssignmentSelectionStatus.NoDiscoveredProduct => Release1SmallCourtesyReviewStatus.NoDiscoveredProduct, Release1SmallCourtesyAssignmentSelectionStatus.NoEmptyDeadDrop => Release1SmallCourtesyReviewStatus.NoEmptyDeadDrop, _ => Release1SmallCourtesyReviewStatus.Unavailable, }; } quote = new Release1SmallCourtesyQuote(assignment, (release1SmallCourtesyAssignmentMode == Release1SmallCourtesyAssignmentMode.Recovery) ? ((double?)null) : new double?(24.0)); quote.Validate(); return Release1SmallCourtesyReviewStatus.Ready; } private void RefreshOfferStatus() { if (_disposed) { OfferStatus = Release1SmallCourtesyOfferStatus.Disposed; } else if (!_loadActive) { OfferStatus = Release1SmallCourtesyOfferStatus.Inactive; } else if (_saving) { OfferStatus = Release1SmallCourtesyOfferStatus.Saving; } else if (_dismissed) { OfferStatus = Release1SmallCourtesyOfferStatus.Dismissed; } else { OfferStatus = Map(TryBuildQuote(out Release1SmallCourtesyQuote _)); } } private Release1SmallCourtesyReviewStatus ReadMatchingContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1SmallCourtesyReviewStatus.Inactive; } try { Release1StoryHostContextSnapshot context2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadContext(out context2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!SameContext(snapshot, context2)) { return Release1SmallCourtesyReviewStatus.Inactive; } context = snapshot; return Release1SmallCourtesyReviewStatus.Ready; } catch { return Release1SmallCourtesyReviewStatus.Unavailable; } } private bool TryReadGameHours(out double gameHours) { gameHours = 0.0; try { if (_world.TryReadCanonicalTotalMinutes(out var totalMinutes) != Release1SmallCourtesyWorldReadStatus.Ready || !double.IsFinite(totalMinutes) || totalMinutes < 0.0) { return false; } gameHours = totalMinutes / 60.0; return double.IsFinite(gameHours); } catch { return false; } } private Release1StoryCommand CreateCommand(Release1TransitionKind transition, int attempt, string receipt, string? termsVersion, double? acceptedHours, double? deadlineHours) { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { throw new InvalidOperationException("Story context was not active."); } return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, "release1.small-courtesy", attempt, transition, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.small-courtesy", attempt, transition, receipt).Value, termsVersion, acceptedHours, deadlineHours); } private void DismissForLoad() { _dismissed = true; _reviewedQuote = null; OfferStatus = Release1SmallCourtesyOfferStatus.Dismissed; } private static string AcceptanceReceipt(Release1SmallCourtesyAssignmentMode mode, int attempt) { return $"small-courtesy-{mode.ToString().ToLowerInvariant()}-accept-v1-a{attempt}"; } private static string ModeToken(Release1MissionState state) { if (state != Release1MissionState.Active) { return "make-good"; } return "primary"; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private static Release1SmallCourtesyReviewResult Review(Release1SmallCourtesyReviewStatus status, string message) { return new Release1SmallCourtesyReviewResult(status, null, message); } private static Release1SmallCourtesyDecisionResult Decision(Release1SmallCourtesyDecisionStatus status, string message) { return new Release1SmallCourtesyDecisionResult(status, message); } private static Release1SmallCourtesyReviewStatus Map(Release1SmallCourtesyWorldReadStatus status) { if ((uint)(status - 1) <= 1u) { return Release1SmallCourtesyReviewStatus.Pending; } return Release1SmallCourtesyReviewStatus.Unavailable; } private static Release1SmallCourtesyOfferStatus Map(Release1SmallCourtesyReviewStatus status) { return status switch { Release1SmallCourtesyReviewStatus.Ready => Release1SmallCourtesyOfferStatus.Available, Release1SmallCourtesyReviewStatus.Inactive => Release1SmallCourtesyOfferStatus.Inactive, Release1SmallCourtesyReviewStatus.Ineligible => Release1SmallCourtesyOfferStatus.Ineligible, Release1SmallCourtesyReviewStatus.Pending => Release1SmallCourtesyOfferStatus.Pending, Release1SmallCourtesyReviewStatus.Saving => Release1SmallCourtesyOfferStatus.Saving, Release1SmallCourtesyReviewStatus.NoDiscoveredProduct => Release1SmallCourtesyOfferStatus.NoDiscoveredProduct, Release1SmallCourtesyReviewStatus.NoEmptyDeadDrop => Release1SmallCourtesyOfferStatus.NoEmptyDeadDrop, Release1SmallCourtesyReviewStatus.Dismissed => Release1SmallCourtesyOfferStatus.Dismissed, Release1SmallCourtesyReviewStatus.Disposed => Release1SmallCourtesyOfferStatus.Disposed, _ => Release1SmallCourtesyOfferStatus.Unavailable, }; } private static string Message(Release1SmallCourtesyReviewStatus status) { return status switch { Release1SmallCourtesyReviewStatus.NoDiscoveredProduct => "No discovered product is available for assignment.", Release1SmallCourtesyReviewStatus.NoEmptyDeadDrop => "No empty dead drop is available for assignment.", Release1SmallCourtesyReviewStatus.Pending => "World eligibility is still pending.", Release1SmallCourtesyReviewStatus.Ineligible => "Small Courtesy is not currently offerable.", Release1SmallCourtesyReviewStatus.Inactive => "The canonical story context is inactive.", _ => "Small Courtesy world data is unavailable.", }; } } public enum Release1SmallCourtesyCardStage { Hidden, Offer, Review, NoEmptyDrop, PersistenceDeferred, DecisionRejected, AwaitingActivation, Active, AwaitingFirstSave, AwaitingSecondSave, AwaitingFinalSave, Ambiguous, Completed } public sealed record Release1SmallCourtesyViewModel(bool Visible, Release1SmallCourtesyCardStage Stage, string Title, string Body, bool CanReview, bool CanAccept, bool CanDefer); public static class Release1SmallCourtesyPresentation { public const string Title = "Small Courtesy"; public const string OfferText = "One package, one drop, one time. Nothing you have not done before. Don't mess it up"; public const string NoEmptyDropText = "Nowhere clean to put it right now. Try me again when the town has room."; public const string QuestionText = "Are you in?"; public const string PaymentText = "I pay 125 percent of what you actually leave, not the asking price. Consider this good faith and don't make me regret it."; public static Release1SmallCourtesyViewModel Build(Release1StoryState? state, long lastPersistedRevision, Release1SmallCourtesyOfferStatus offerStatus, Release1SmallCourtesyQuote? reviewedQuote, string? feedback = null, Release1SmallCourtesyCardStage? feedbackStage = null) { if (!string.IsNullOrWhiteSpace(feedback)) { return Visible(feedbackStage ?? Release1SmallCourtesyCardStage.PersistenceDeferred, feedback); } if (offerStatus == Release1SmallCourtesyOfferStatus.NoEmptyDeadDrop) { return Visible(Release1SmallCourtesyCardStage.NoEmptyDrop, "Nowhere clean to put it right now. Try me again when the town has room."); } if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Hidden(); } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; Release1SmallCourtesyAssignment release1SmallCourtesyAssignment = (from candidate in state.SmallCourtesyAssignments where candidate.Attempt == mission.Attempt orderby candidate.Attempt descending select candidate).FirstOrDefault(); if ((object)release1SmallCourtesyAssignment == null) { if ((object)reviewedQuote != null) { return Terms(reviewedQuote.Assignment, reviewedQuote.DeadlineDurationHours, review: true); } if (offerStatus == Release1SmallCourtesyOfferStatus.Available) { return new Release1SmallCourtesyViewModel(Visible: true, Release1SmallCourtesyCardStage.Offer, "Small Courtesy", "One package, one drop, one time. Nothing you have not done before. Don't mess it up", CanReview: true, CanAccept: false, CanDefer: true); } return Hidden(); } if (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.small-courtesy" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked)) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.Ambiguous, "Stopped on conflicting evidence. It will not retry."); } if (mission.State == Release1MissionState.Accepted) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.AwaitingActivation, "Accepted."); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry effect) => effect.Attempt == mission.Attempt && string.Equals(effect.EffectKind, "CargoTransfer", StringComparison.Ordinal)); Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry effect) => effect.Attempt == mission.Attempt && string.Equals(effect.EffectKind, "Reward", StringComparison.Ordinal)); if ((object)release1NativeEffectJournalEntry == null) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.Active, "Deliver the assigned package."); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.AwaitingFirstSave, (release1NativeEffectJournalEntry.PreparedStoryRevision > lastPersistedRevision) ? "Deposit recognized. Save once to complete pickup." : "Deposit prepared. Waiting for reconciliation."); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied || ((object)release1NativeEffectJournalEntry2 != null && release1NativeEffectJournalEntry2.Phase == Release1NativeEffectPhase.Prepared)) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.AwaitingSecondSave, "Item accepted. Save again to authorize payment."); } if ((object)release1NativeEffectJournalEntry2 != null && release1NativeEffectJournalEntry2.Phase == Release1NativeEffectPhase.Applied) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.AwaitingFinalSave, "Payment delivered. Save once more to finalize."); } if (((object)release1NativeEffectJournalEntry2 != null && release1NativeEffectJournalEntry2.Phase == Release1NativeEffectPhase.Committed) || (mission.State == Release1MissionState.Satisfied && release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Committed)) { return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.Completed, "Complete. Delivery and payment are saved."); } return Terms(release1SmallCourtesyAssignment, DeadlineDuration(mission), review: false, Release1SmallCourtesyCardStage.Active, "Delivery active."); } private static Release1SmallCourtesyViewModel Terms(Release1SmallCourtesyAssignment assignment, double? deadlineHours, bool review, Release1SmallCourtesyCardStage? stage = null, string? status = null) { string text = Release1PlayerCopy.Deadline(deadlineHours); string text2 = Release1PlayerCopy.AttemptHeader(assignment.Mode, assignment.Attempt); string body = (review ? Copy(Release1PlayerCopy.JoinLines(text2, "1 " + assignment.PackagingName + " of " + assignment.ProductName, "Drop: " + assignment.DeadDropName + ", empty when I picked it.", "Deadline: " + text, "I pay 125 percent of what you actually leave, not the asking price. Consider this good faith and don't make me regret it.", "Are you in?")) : Copy(Release1PlayerCopy.JoinLines("Status: " + (status ?? "Delivery active."), "Deliver: 1 " + assignment.PackagingName + " of " + assignment.ProductName, "Drop: " + assignment.DeadDropName, "Deadline: " + text, "I pay 125 percent of what you actually leave, not the asking price. Consider this good faith and don't make me regret it."))); return new Release1SmallCourtesyViewModel(Visible: true, stage ?? Release1SmallCourtesyCardStage.Review, "Small Courtesy", body, CanReview: false, review, review); } private static double? DeadlineDuration(Release1MissionRecord mission) { if (mission.DeadlineGameTimeHours.HasValue && mission.AcceptedGameTimeHours.HasValue) { return mission.DeadlineGameTimeHours.Value - mission.AcceptedGameTimeHours.Value; } return null; } private static Release1SmallCourtesyViewModel Visible(Release1SmallCourtesyCardStage stage, string body) { return new Release1SmallCourtesyViewModel(Visible: true, stage, "Small Courtesy", body, CanReview: false, CanAccept: false, CanDefer: false); } private static Release1SmallCourtesyViewModel Hidden() { return new Release1SmallCourtesyViewModel(Visible: false, Release1SmallCourtesyCardStage.Hidden, "Small Courtesy", string.Empty, CanReview: false, CanAccept: false, CanDefer: false); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1SmallCourtesyPresenter : IDisposable { private readonly Release1SmallCourtesyMissionService _service; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private string? _feedback; private Release1SmallCourtesyCardStage? _feedbackStage; private bool _disposed; public Release1SmallCourtesyViewModel View { get { if (!_disposed) { return Release1SmallCourtesyPresentation.Build(_story.State, _story.LastPersistedRevision, _service.OfferStatus, _service.ReviewedQuote, _feedback, _feedbackStage); } return Release1SmallCourtesyPresentation.Build(null, -1L, Release1SmallCourtesyOfferStatus.Disposed, null); } } public Release1DecisionPromptCard? DecisionPrompt { get { Release1SmallCourtesyViewModel view = View; if (!view.Visible) { return null; } if (view.CanReview) { return new Release1DecisionPromptCard("release1-small-courtesy-review", "Small Courtesy is ready for review.", view.Title, view.Body, "Review terms", delegate { TryReview(); }); } if (view.CanAccept) { return new Release1DecisionPromptCard("release1-small-courtesy-decision", "Small Courtesy is waiting for your decision.", view.Title, view.Body, "Accept", delegate { TryAccept(); }, "Not now", delegate { TryDefer(); }); } return null; } } public Release1SmallCourtesyPresenter(Release1SmallCourtesyMissionService service, Release1StoryRuntimeService story, Action? log = null) { _service = service ?? throw new ArgumentNullException("service"); _story = story ?? throw new ArgumentNullException("story"); _log = log; } public Release1SmallCourtesyReviewResult TryReview() { Release1SmallCourtesyReviewResult release1SmallCourtesyReviewResult = _service.TryReview(); _feedback = ((release1SmallCourtesyReviewResult.Status == Release1SmallCourtesyReviewStatus.Ready) ? null : release1SmallCourtesyReviewResult.Message); _feedbackStage = ((_feedback == null) ? ((Release1SmallCourtesyCardStage?)null) : new Release1SmallCourtesyCardStage?((release1SmallCourtesyReviewResult.Status == Release1SmallCourtesyReviewStatus.NoEmptyDeadDrop) ? Release1SmallCourtesyCardStage.NoEmptyDrop : Release1SmallCourtesyCardStage.DecisionRejected)); if (_feedback != null) { _log?.Invoke($"Small Courtesy review failed: {release1SmallCourtesyReviewResult.Status} — {release1SmallCourtesyReviewResult.Message}"); } return release1SmallCourtesyReviewResult; } public Release1SmallCourtesyDecisionResult TryAccept() { return Handle(_service.TryAccept()); } public Release1SmallCourtesyDecisionResult TryDefer() { return Handle(_service.TryDefer()); } public void ClearFeedback() { _feedback = null; _feedbackStage = null; } public void Dispose() { _feedback = null; _feedbackStage = null; _disposed = true; } private Release1SmallCourtesyDecisionResult Handle(Release1SmallCourtesyDecisionResult result) { Release1SmallCourtesyDecisionStatus status = result.Status; if ((uint)status <= 2u) { _feedback = null; _feedbackStage = null; } else { _feedback = result.Message; _feedbackStage = ((result.Status == Release1SmallCourtesyDecisionStatus.PersistenceDeferred) ? Release1SmallCourtesyCardStage.PersistenceDeferred : Release1SmallCourtesyCardStage.DecisionRejected); _log?.Invoke($"Small Courtesy decision failed: {result.Status} — {result.Message}"); } return result; } } public sealed class Release1SmallCourtesyQuest : Quest { public const string DisplayTitle = "Small Courtesy"; private readonly QuestEntry[] _entries; protected override string Title => Release1PlayerCopy.Normalize("Small Courtesy"); protected override string Description => Release1PlayerCopy.Normalize("Small Courtesy"); public Release1SmallCourtesyQuest() { string text = Release1PlayerCopy.Normalize("Small Courtesy"); _entries = (QuestEntry[])(object)new QuestEntry[2] { ((Quest)this).AddEntry(text, (Vector3?)null), ((Quest)this).AddEntry(text, (Vector3?)null) }; } public void Project(Release1DesiredQuest quest) { ArgumentNullException.ThrowIfNull(quest, "quest"); if (quest.Entries.Count != _entries.Length) { throw new InvalidOperationException($"Release 1 Small Courtesy quest desired entry count ({quest.Entries.Count}) does not match the quest's native entry count ({_entries.Length})."); } for (int i = 0; i < _entries.Length; i++) { ApplyEntry(_entries[i], quest.Entries[i]); } } public IReadOnlyList ReadEntryStates() { return _entries.Select((QuestEntry entry) => MapState(entry.State)).ToArray(); } private static void ApplyEntry(QuestEntry entry, Release1DesiredEntry desired) { //IL_0035: 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) entry.Title = Release1PlayerCopy.Normalize(desired.Text); Release1WorldPoint marker = desired.Marker; entry.POIPosition = (Vector3)(((object)marker != null) ? new Vector3(marker.X, marker.Y, marker.Z) : Vector3.zero); switch (Release1NativePresentationSupport.ClassifyEntryAction(desired.State)) { case Release1QuestEntryProjectionAction.Begin: entry.Begin(); break; case Release1QuestEntryProjectionAction.Complete: entry.Complete(); break; default: entry.SetState((QuestState)0); break; } } private static Release1DesiredEntryState MapState(QuestState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 1) { if ((int)state == 2) { return Release1DesiredEntryState.Complete; } return Release1DesiredEntryState.Inactive; } return Release1DesiredEntryState.Active; } } public static class Release1StoryHostIdentity { public static Release1StoryHostContextReadStatus Resolve(string? activeSaveFolder, string? playerCode, out string? canonicalIdentity) { canonicalIdentity = null; if (string.IsNullOrWhiteSpace(activeSaveFolder)) { return Release1StoryHostContextReadStatus.Pending; } if (!LocalPressureHostIdentity.TryGetCanonicalHostIdentity(activeSaveFolder, out string identity) || identity == null) { return Release1StoryHostContextReadStatus.AmbiguousIdentity; } string text = playerCode?.Trim(); if (!LocalPressureHostIdentity.IsCanonicalPlayerIdentity(text)) { return Release1StoryHostContextReadStatus.Pending; } if (!string.Equals(identity, text, StringComparison.Ordinal)) { return Release1StoryHostContextReadStatus.AmbiguousIdentity; } canonicalIdentity = identity; return Release1StoryHostContextReadStatus.Ready; } } internal enum Release1StoryOwnerQaRecognitionStatus { Recognized, AlreadyRecognized, Rejected } internal sealed record Release1StoryOwnerQaRecognitionResult(Release1StoryOwnerQaRecognitionStatus Status, string Message); internal static class Release1StoryOwnerQaRecognitionHarness { private const string ReceiptPrefix = "oc48-owner-qa"; public static Release1StoryOwnerQaRecognitionResult TryRecognize(IRelease1StoryHostContext context, Release1StoryRuntimeService service) { ArgumentNullException.ThrowIfNull(context, "context"); ArgumentNullException.ThrowIfNull(service, "service"); Release1StoryHostContextReadStatus release1StoryHostContextReadStatus; Release1StoryHostContextSnapshot snapshot; try { release1StoryHostContextReadStatus = context.TryRead(out snapshot); } catch (Exception ex) { return Reject("Owner QA host context faulted: " + ex.Message); } if (release1StoryHostContextReadStatus != Release1StoryHostContextReadStatus.Ready) { return Reject($"Owner QA recognition rejected by host context: {release1StoryHostContextReadStatus}."); } Release1StoryState? state = service.State; if ((object)state != null && state.Release1Recognized) { return new Release1StoryOwnerQaRecognitionResult(Release1StoryOwnerQaRecognitionStatus.AlreadyRecognized, "Release 1 was already recognized; no story transition ran."); } if ((object)service.State == null || service.State.RelationshipState == Release1RelationshipState.Deferred) { Release1StoryCommand command = Command(snapshot, "release1.intro", 0, Release1TransitionKind.IntroAccepted, "oc48-owner-qa-intro"); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = service.TryExecute(command); if (!release1StoryRuntimeCommandResult.Accepted) { return Reject(release1StoryRuntimeCommandResult.Message); } } Release1StoryState? state2 = service.State; if ((object)state2 == null || state2.RelationshipState != Release1RelationshipState.Accepted) { return Reject("Owner QA recognition requires an accepted Release 1 relationship."); } for (int i = 0; i < Release1MissionCatalog.All.Count; i++) { int num = i + 1; while (service.State.Missions[i].State != Release1MissionState.Satisfied) { Release1MissionRecord release1MissionRecord = service.State.Missions[i]; Release1StoryCommand release1StoryCommand = release1MissionRecord.State switch { Release1MissionState.Offered => MissionCommand(snapshot, release1MissionRecord, num, Release1TransitionKind.MissionAccepted, "accepted", "oc48-owner-qa-v1"), Release1MissionState.Accepted => MissionCommand(snapshot, release1MissionRecord, num, Release1TransitionKind.MissionActivated, "activated"), Release1MissionState.Active => MissionCommand(snapshot, release1MissionRecord, num, Release1TransitionKind.MissionCompleted, "completed", null, Release1CompletionTiming.OnTime, $"{"oc48-owner-qa"}-{num}-reward", (i == Release1MissionCatalog.All.Count - 1) ? "oc48-owner-qa-recognition" : null), _ => null, }; if ((object)release1StoryCommand == null) { return Reject($"Mission {release1MissionRecord.MissionKey} was in unsupported QA state {release1MissionRecord.State}; no bypass was attempted."); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = service.TryExecute(release1StoryCommand); if (!release1StoryRuntimeCommandResult2.Accepted) { return Reject($"Mission {release1MissionRecord.MissionKey} rejected {release1StoryCommand.TransitionKind}: {release1StoryRuntimeCommandResult2.Message}"); } } } Release1StoryState? state3 = service.State; if ((object)state3 == null || !state3.Release1Recognized) { return Reject("Canonical mission transitions completed without a recognition receipt."); } return new Release1StoryOwnerQaRecognitionResult(Release1StoryOwnerQaRecognitionStatus.Recognized, "Release 1 recognition was reached through the six canonical mission transition sequences."); } private static Release1StoryCommand MissionCommand(Release1StoryHostContextSnapshot snapshot, Release1MissionRecord mission, int sequence, Release1TransitionKind kind, string suffix, string? termsVersion = null, Release1CompletionTiming? completionTiming = null, string? rewardReceipt = null, string? recognitionReceipt = null) { return Command(snapshot, mission.MissionKey, mission.Attempt, kind, $"{"oc48-owner-qa"}-{sequence}-{suffix}", termsVersion, completionTiming, rewardReceipt, recognitionReceipt); } private static Release1StoryCommand Command(Release1StoryHostContextSnapshot snapshot, string missionKey, int attempt, Release1TransitionKind kind, string receipt, string? termsVersion = null, Release1CompletionTiming? completionTiming = null, string? rewardReceipt = null, string? recognitionReceipt = null) { return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, missionKey, attempt, kind, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, missionKey, attempt, kind, receipt).Value, termsVersion, null, null, completionTiming, rewardReceipt, null, recognitionReceipt); } private static Release1StoryOwnerQaRecognitionResult Reject(string message) { return new Release1StoryOwnerQaRecognitionResult(Release1StoryOwnerQaRecognitionStatus.Rejected, message); } } public enum Release1StoryHostContextReadStatus { Ready, Pending, NotAuthoritative, UnsupportedMultiplayer, AmbiguousIdentity, Faulted } public readonly record struct Release1StoryHostContextSnapshot(Guid SessionEpoch, long LoadEpoch, string PlayerId, string ActiveSaveFolder); public interface IRelease1StoryHostContext { Release1StoryHostContextReadStatus TryRead(out Release1StoryHostContextSnapshot snapshot); } public interface IRelease1StoryRepository { Release1StoryStoreLoadResult Load(); Release1StoryStoreUpdateResult Update(Release1StoryState? state); } public interface IRelease1StorySaveFolderBoundRepository { string BoundSaveFolder { get; } } public interface IRelease1StoryRawContentPreservingRepository { Release1StoryStoreUpdateResult PreserveRawContents(string rawContents); } public interface IRelease1StoryRepositoryFactory { IRelease1StoryRepository Create(string activeSaveFolder); } public sealed class Release1StoryStateStoreRepository : IRelease1StoryRepository, IRelease1StorySaveFolderBoundRepository, IRelease1StoryRawContentPreservingRepository { private readonly Release1StoryStateStore _store; public string BoundSaveFolder => _store.SavePath.ActiveSaveFolder; public Release1StoryStateStoreRepository(Release1StoryStateStore store) { _store = store; } public Release1StoryStoreLoadResult Load() { _store.TryLoad(out Release1StoryStoreLoadResult result); return result; } public Release1StoryStoreUpdateResult Update(Release1StoryState? state) { _store.TryUpdate(state, out Release1StoryStoreUpdateResult result); return result; } public Release1StoryStoreUpdateResult PreserveRawContents(string rawContents) { _store.TryPreserveRawContents(rawContents, out Release1StoryStoreUpdateResult result); return result; } } public enum Release1StoryRuntimePhase { Detached, AwaitingLoad, Active, Saving, Quarantined, Disposed } public enum Release1StoryCommandStatus { Accepted, NoOp, DeferredSaving, Rejected } public enum Release1StoryRuntimeRejectReason { None, Inactive, ContextPending, NotAuthoritative, UnsupportedMultiplayer, AmbiguousIdentity, ContextFaulted, WrongEpoch, IdentityMismatch, InvalidTransition, SidecarLoadFailed, SidecarSaveFailed, Quarantined, Disposed, LifecycleTransition, RepositoryUnbound, RepositoryPathMismatch, RepositoryFaulted, EffectNotFound, EffectAmbiguous } public sealed record Release1StoryRuntimeCommandResult(Release1StoryCommandStatus Status, Release1StoryRuntimeRejectReason RejectReason, Release1StoryState? State, string Message) { public bool Accepted { get { Release1StoryCommandStatus status = Status; if ((uint)status <= 1u) { return true; } return false; } } } public enum Release1NativeEffectIssuanceOutcome { Applied, Failed, Ambiguous } public enum Release1NativeEffectPersistenceMode { SaveGated, RevertTolerant } public sealed record Release1StoryRuntimeLifecycleResult(Release1StoryRuntimePhase Phase, Release1StoryRuntimeRejectReason RejectReason, string Message); public static class Release1StoryLifecycleLogging { public static void LogIfRejected(string phase, Release1StoryRuntimeLifecycleResult? result) { if ((object)result != null && result.RejectReason != Release1StoryRuntimeRejectReason.None) { OrganizedCrimeLog.Warning($"Story runtime {phase} rejected: {result.RejectReason}: {result.Message}"); } } } public sealed class Release1StoryRuntimeService : IDisposable { private const string SmallCourtesyTermsVersion = "small-courtesy-v1"; private const string WrongAddressTermsVersion = "wrong-address-v1"; private const string RoomWithNoNameTermsVersion = "room-with-no-name-v1"; private const string ShortNoticeTermsVersion = "short-notice-v1"; private const string KeepTheLightsOffTermsVersion = "keep-the-lights-off-v1"; private const string TheEnvelopeTermsVersion = "the-envelope-v1"; private readonly object _gate = new object(); private readonly IRelease1StoryHostContext _context; private IRelease1StoryRepository _repository; private readonly Func? _repositoryFactory; private Release1StoryState? _state; private Release1StoryHostContextSnapshot _activeContext; private Release1StoryState? _saveSnapshot; private Release1StoryRuntimePhase _phase; private Release1StoryRuntimePhase _phaseBeforeSave; private bool _disposed; private Release1StoryRuntimeRejectReason _lastLifecycleRejectReason; private string? _quarantinedRawContents; public Release1StoryRuntimePhase Phase { get { lock (_gate) { return _phase; } } } public Release1StoryState? State { get { lock (_gate) { return _state; } } } public long LastPersistedRevision { get; private set; } = -1L; public Release1StoryRuntimeRejectReason LastLifecycleRejectReason => _lastLifecycleRejectReason; public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public Release1StoryRuntimeService(IRelease1StoryHostContext context, IRelease1StoryRepository repository) { _context = context ?? throw new ArgumentNullException("context"); _repository = repository ?? throw new ArgumentNullException("repository"); } public Release1StoryRuntimeService(IRelease1StoryHostContext context, Func repositoryFactory) { _context = context ?? throw new ArgumentNullException("context"); _repositoryFactory = repositoryFactory ?? throw new ArgumentNullException("repositoryFactory"); _repository = null; } public Release1StoryRuntimeService(IRelease1StoryHostContext context, IRelease1StoryRepositoryFactory repositoryFactory) : this(context, (string folder) => (repositoryFactory ?? throw new ArgumentNullException("repositoryFactory")).Create(folder)) { } public Release1StoryRuntimeLifecycleResult OnPreLoad() { lock (_gate) { if (_disposed) { return Lifecycle(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } if (_phase == Release1StoryRuntimePhase.AwaitingLoad) { return Lifecycle(Release1StoryRuntimeRejectReason.None, "PreLoad was already handled."); } _state = null; _saveSnapshot = null; _quarantinedRawContents = null; _phase = Release1StoryRuntimePhase.AwaitingLoad; LastPersistedRevision = -1L; return Lifecycle(Release1StoryRuntimeRejectReason.None, "Story state was cleared for load."); } } public Release1StoryRuntimeLifecycleResult OnLoadComplete() { lock (_gate) { if (_disposed) { return Lifecycle(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } Release1StoryRuntimePhase phase = _phase; if ((uint)(phase - 2) <= 1u) { return Lifecycle(Release1StoryRuntimeRejectReason.None, "LoadComplete was already handled."); } Release1StoryHostContextReadStatus release1StoryHostContextReadStatus; Release1StoryHostContextSnapshot snapshot; try { release1StoryHostContextReadStatus = _context.TryRead(out snapshot); } catch (Exception ex) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.ContextFaulted, ex.Message); } switch (release1StoryHostContextReadStatus) { case Release1StoryHostContextReadStatus.Pending: return Lifecycle(Release1StoryRuntimeRejectReason.ContextPending, "Host context is pending."); case Release1StoryHostContextReadStatus.NotAuthoritative: return Lifecycle(Release1StoryRuntimeRejectReason.NotAuthoritative, "Host authority is still settling during load."); default: _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Map(release1StoryHostContextReadStatus), "Host context was not safe for story activation."); case Release1StoryHostContextReadStatus.Ready: { if (_repositoryFactory != null) { try { _repository = _repositoryFactory(snapshot.ActiveSaveFolder) ?? throw new InvalidOperationException("Repository factory returned null."); } catch (Exception ex2) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex2.Message); } } if (!(_repository is IRelease1StorySaveFolderBoundRepository release1StorySaveFolderBoundRepository)) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryUnbound, "Repository was not bound to a validated save folder."); } try { if (!SameSaveFolder(release1StorySaveFolderBoundRepository.BoundSaveFolder, snapshot.ActiveSaveFolder)) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryPathMismatch, "Repository save folder did not match host context."); } } catch (Exception ex3) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex3.Message); } Release1StoryStoreLoadResult release1StoryStoreLoadResult; try { release1StoryStoreLoadResult = _repository.Load(); } catch (Exception ex4) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex4.Message); } if (!release1StoryStoreLoadResult.Succeeded) { _phase = Release1StoryRuntimePhase.Quarantined; _quarantinedRawContents = release1StoryStoreLoadResult.RawContents; return Lifecycle(Release1StoryRuntimeRejectReason.SidecarLoadFailed, release1StoryStoreLoadResult.Message); } if ((object)release1StoryStoreLoadResult.Envelope?.Story != null && release1StoryStoreLoadResult.Envelope.Story.PlayerId != snapshot.PlayerId) { _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.IdentityMismatch, "Story sidecar identity did not match host context."); } _activeContext = snapshot; _state = release1StoryStoreLoadResult.Envelope?.Story; LastPersistedRevision = _state?.Revision ?? (-1); _phase = Release1StoryRuntimePhase.Active; RecoverAttemptingPhonePresentations(); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = ReconcileSmallCourtesyOnLoad(); if ((object)release1StoryRuntimeCommandResult != null && release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.Rejected) { return Lifecycle(release1StoryRuntimeCommandResult.RejectReason, release1StoryRuntimeCommandResult.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = ReconcileWrongAddressOnLoad(); if ((object)release1StoryRuntimeCommandResult2 != null && release1StoryRuntimeCommandResult2.Status == Release1StoryCommandStatus.Rejected) { return Lifecycle(release1StoryRuntimeCommandResult2.RejectReason, release1StoryRuntimeCommandResult2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult3 = ReconcileRoomWithNoNameOnLoad(); if ((object)release1StoryRuntimeCommandResult3 != null && release1StoryRuntimeCommandResult3.Status == Release1StoryCommandStatus.Rejected) { return Lifecycle(release1StoryRuntimeCommandResult3.RejectReason, release1StoryRuntimeCommandResult3.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult4 = ReconcileShortNoticeOnLoad(); if ((object)release1StoryRuntimeCommandResult4 != null && release1StoryRuntimeCommandResult4.Status == Release1StoryCommandStatus.Rejected) { return Lifecycle(release1StoryRuntimeCommandResult4.RejectReason, release1StoryRuntimeCommandResult4.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult5 = ReconcileKeepTheLightsOffOnLoad(); if ((object)release1StoryRuntimeCommandResult5 != null && release1StoryRuntimeCommandResult5.Status == Release1StoryCommandStatus.Rejected) { return Lifecycle(release1StoryRuntimeCommandResult5.RejectReason, release1StoryRuntimeCommandResult5.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult6 = ReconcileTheEnvelopeOnLoad(); if ((object)release1StoryRuntimeCommandResult6 != null && release1StoryRuntimeCommandResult6.Status == Release1StoryCommandStatus.Rejected) { return Lifecycle(release1StoryRuntimeCommandResult6.RejectReason, release1StoryRuntimeCommandResult6.Message); } return Lifecycle(Release1StoryRuntimeRejectReason.None, "Story runtime is active."); } } } } public Release1StoryRuntimeLifecycleResult OnSaveStart() { lock (_gate) { if (_disposed) { return Lifecycle(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } if (_phase == Release1StoryRuntimePhase.Saving) { return Lifecycle(Release1StoryRuntimeRejectReason.None, "SaveStart was already handled."); } if (_phase == Release1StoryRuntimePhase.Quarantined) { return PreserveQuarantinedSidecar(); } if (_phase != Release1StoryRuntimePhase.Active) { return Lifecycle(Release1StoryRuntimeRejectReason.Inactive, "Only an active story can save."); } _phaseBeforeSave = _phase; _saveSnapshot = _state; _phase = Release1StoryRuntimePhase.Saving; return Lifecycle(Release1StoryRuntimeRejectReason.None, "Story save snapshot captured."); } } public Release1StoryRuntimeLifecycleResult OnSaveComplete() { lock (_gate) { if (_disposed) { return Lifecycle(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } if (_phase != Release1StoryRuntimePhase.Saving) { return Lifecycle(Release1StoryRuntimeRejectReason.None, "SaveComplete was a lifecycle no-op."); } Release1StoryStoreUpdateResult release1StoryStoreUpdateResult; try { release1StoryStoreUpdateResult = Timing.Measure("story-sidecar-write/save-complete", () => _repository.Update(_saveSnapshot)); } catch (Exception ex) { _saveSnapshot = null; _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex.Message); } if (!release1StoryStoreUpdateResult.Succeeded) { _saveSnapshot = null; _phase = Release1StoryRuntimePhase.Quarantined; return Lifecycle(Release1StoryRuntimeRejectReason.SidecarSaveFailed, release1StoryStoreUpdateResult.Message); } LastPersistedRevision = _saveSnapshot?.Revision ?? (-1); _saveSnapshot = null; _phase = _phaseBeforeSave; return Lifecycle(Release1StoryRuntimeRejectReason.None, "Story save snapshot persisted."); } } public Release1StoryRuntimeCommandResult TryExecute(Release1StoryCommand command) { lock (_gate) { return TryExecuteCore(command, persistImmediately: false); } } public Release1StoryRuntimeCommandResult TryExecuteDurably(Release1StoryCommand command) { lock (_gate) { return TryExecuteCore(command, persistImmediately: true); } } private Release1StoryRuntimeCommandResult TryExecuteCore(Release1StoryCommand command, bool persistImmediately) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if ((object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted story transition did not return state."); } if (release1StoryTransitionResult.Idempotent) { if (!persistImmediately || LastPersistedRevision >= release1StoryTransitionResult.State.Revision) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, release1StoryTransitionResult.Message); } return PersistState(release1StoryTransitionResult.State, "Idempotent story transition was persisted."); } if (persistImmediately) { return PersistState(release1StoryTransitionResult.State, "Story transition was persisted."); } _state = release1StoryTransitionResult.State; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, release1StoryTransitionResult.Message); } public Release1StoryRuntimeCommandResult TryExecuteSmallCourtesyAcceptanceDurably(Release1StoryCommand command, Release1SmallCourtesyAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Small Courtesy assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1TransitionKind? release1TransitionKind = assignment.Mode switch { Release1SmallCourtesyAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1SmallCourtesyAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1SmallCourtesyAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => null, }; if (!release1TransitionKind.HasValue || command.TransitionKind != release1TransitionKind || command.MissionKey != "release1.small-courtesy" || assignment.MissionKey != command.MissionKey || assignment.Attempt != command.Attempt || assignment.AuthorizationCorrelationId != command.CorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Small Courtesy assignment did not match its acceptance command."); } if (assignment.Mode == Release1SmallCourtesyAssignmentMode.Primary && !string.Equals(command.TermsVersion, "small-courtesy-v1", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Small Courtesy primary terms version was not canonical."); } Release1SmallCourtesyAssignment release1SmallCourtesyAssignment = _state.SmallCourtesyAssignments.SingleOrDefault((Release1SmallCourtesyAssignment candidate) => candidate.Attempt == assignment.Attempt); if ((object)release1SmallCourtesyAssignment != null && release1SmallCourtesyAssignment != assignment) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Small Courtesy attempt already had a different assignment."); } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted || (object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if (release1StoryTransitionResult.Idempotent) { if ((object)release1SmallCourtesyAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted Small Courtesy correlation had no atomic assignment."); } return ReconcileAcceptedSmallCourtesy(release1SmallCourtesyAssignment); } Release1StoryState release1StoryState; try { release1StoryState = release1StoryTransitionResult.State with { SmallCourtesyAssignments = release1StoryTransitionResult.State.SmallCourtesyAssignments.Append(assignment).ToArray() }; release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = PersistState(release1StoryState, "Small Courtesy acceptance and assignment were persisted atomically."); if (!release1StoryRuntimeCommandResult2.Accepted) { return release1StoryRuntimeCommandResult2; } return ReconcileAcceptedSmallCourtesy(assignment); } } public Release1StoryRuntimeCommandResult TryExecuteWrongAddressAcceptanceDurably(Release1StoryCommand command, Release1WrongAddressAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Wrong Address assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1TransitionKind? release1TransitionKind = assignment.Mode switch { Release1WrongAddressAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1WrongAddressAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1WrongAddressAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => null, }; if (!release1TransitionKind.HasValue || command.TransitionKind != release1TransitionKind || command.MissionKey != "release1.wrong-address" || assignment.MissionKey != command.MissionKey || assignment.Attempt != command.Attempt || assignment.AuthorizationCorrelationId != command.CorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Wrong Address assignment did not match its acceptance command."); } if (assignment.Mode == Release1WrongAddressAssignmentMode.Primary && !string.Equals(command.TermsVersion, "wrong-address-v1", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Wrong Address primary terms version was not canonical."); } Release1WrongAddressAssignment release1WrongAddressAssignment = _state.WrongAddressAssignments.SingleOrDefault((Release1WrongAddressAssignment candidate) => candidate.Attempt == assignment.Attempt); if ((object)release1WrongAddressAssignment != null && release1WrongAddressAssignment != assignment) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Wrong Address attempt already had a different assignment."); } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted || (object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if (release1StoryTransitionResult.Idempotent) { if ((object)release1WrongAddressAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted Wrong Address correlation had no atomic assignment."); } return ReconcileAcceptedWrongAddress(release1WrongAddressAssignment); } Release1StoryState release1StoryState; try { release1StoryState = release1StoryTransitionResult.State with { WrongAddressAssignments = release1StoryTransitionResult.State.WrongAddressAssignments.Append(assignment).ToArray() }; release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = PersistState(release1StoryState, "Wrong Address acceptance and assignment were persisted atomically."); if (!release1StoryRuntimeCommandResult2.Accepted) { return release1StoryRuntimeCommandResult2; } return ReconcileAcceptedWrongAddress(assignment); } } private Release1StoryRuntimeCommandResult ReconcileAcceptedWrongAddress(Release1WrongAddressAssignment assignment) { if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; if (assignment.Mode != Release1WrongAddressAssignmentMode.Primary || release1MissionRecord.State != Release1MissionState.Accepted) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Wrong Address stage was already active."); } string receiptId = $"wrong-address-activate-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.wrong-address", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.wrong-address", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } private Release1StoryRuntimeCommandResult? ReconcileWrongAddressOnLoad() { if ((object)_state == null) { return null; } Release1MissionRecord mission = _state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; if (mission.State == Release1MissionState.Accepted) { Release1WrongAddressAssignment release1WrongAddressAssignment = _state.WrongAddressAssignments.SingleOrDefault((Release1WrongAddressAssignment candidate) => candidate.Attempt == mission.Attempt && candidate.Mode == Release1WrongAddressAssignmentMode.Primary); if ((object)release1WrongAddressAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, $"Wrong Address mission was Accepted on load but attempt {mission.Attempt} had no assignment on file."); } return ReconcileAcceptedWrongAddress(release1WrongAddressAssignment); } if (mission.State != Release1MissionState.Deferred) { return null; } string receiptId = $"wrong-address-reoffer-v1-a{mission.Attempt + 1}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.wrong-address", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.wrong-address", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } public Release1StoryRuntimeCommandResult TryExecuteRoomWithNoNameAcceptanceDurably(Release1StoryCommand command, Release1RoomWithNoNameAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Room With No Name assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1TransitionKind? release1TransitionKind = assignment.Mode switch { Release1RoomWithNoNameAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1RoomWithNoNameAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1RoomWithNoNameAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => null, }; if (!release1TransitionKind.HasValue || command.TransitionKind != release1TransitionKind || command.MissionKey != "release1.room-with-no-name" || assignment.MissionKey != command.MissionKey || assignment.Attempt != command.Attempt || assignment.AuthorizationCorrelationId != command.CorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Room With No Name assignment did not match its acceptance command."); } if (assignment.Mode == Release1RoomWithNoNameAssignmentMode.Primary && !string.Equals(command.TermsVersion, "room-with-no-name-v1", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Room With No Name primary terms version was not canonical."); } Release1RoomWithNoNameAssignment release1RoomWithNoNameAssignment = _state.RoomWithNoNameAssignments.SingleOrDefault((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt == assignment.Attempt); if ((object)release1RoomWithNoNameAssignment != null && release1RoomWithNoNameAssignment != assignment) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Room With No Name attempt already had a different assignment."); } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted || (object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if (release1StoryTransitionResult.Idempotent) { if ((object)release1RoomWithNoNameAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted Room With No Name correlation had no atomic assignment."); } return ReconcileAcceptedRoomWithNoName(release1RoomWithNoNameAssignment); } Release1StoryState release1StoryState; try { release1StoryState = release1StoryTransitionResult.State with { RoomWithNoNameAssignments = release1StoryTransitionResult.State.RoomWithNoNameAssignments.Append(assignment).ToArray() }; release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = PersistState(release1StoryState, "Room With No Name acceptance and assignment were persisted atomically."); if (!release1StoryRuntimeCommandResult2.Accepted) { return release1StoryRuntimeCommandResult2; } return ReconcileAcceptedRoomWithNoName(assignment); } } private Release1StoryRuntimeCommandResult ReconcileAcceptedRoomWithNoName(Release1RoomWithNoNameAssignment assignment) { if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; if (assignment.Mode != Release1RoomWithNoNameAssignmentMode.Primary || release1MissionRecord.State != Release1MissionState.Accepted) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Room With No Name stage was already active."); } string receiptId = $"room-with-no-name-activate-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.room-with-no-name", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.room-with-no-name", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } private Release1StoryRuntimeCommandResult? ReconcileRoomWithNoNameOnLoad() { if ((object)_state == null) { return null; } Release1MissionRecord mission = _state.Missions[Release1MissionCatalog.IndexOf("release1.room-with-no-name")]; if (mission.State == Release1MissionState.Accepted) { Release1RoomWithNoNameAssignment release1RoomWithNoNameAssignment = _state.RoomWithNoNameAssignments.SingleOrDefault((Release1RoomWithNoNameAssignment candidate) => candidate.Attempt == mission.Attempt && candidate.Mode == Release1RoomWithNoNameAssignmentMode.Primary); if ((object)release1RoomWithNoNameAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, $"Room With No Name mission was Accepted on load but attempt {mission.Attempt} had no assignment on file."); } return ReconcileAcceptedRoomWithNoName(release1RoomWithNoNameAssignment); } if (mission.State != Release1MissionState.Deferred) { return null; } string receiptId = $"room-with-no-name-reoffer-v1-a{mission.Attempt + 1}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.room-with-no-name", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.room-with-no-name", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } public Release1StoryRuntimeCommandResult TrySetRoomWithNoNameProgress(Release1RoomWithNoNameProgress next) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)next == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Room With No Name progress was null."); } try { next.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } if (!_state.RoomWithNoNameAssignments.Any((Release1RoomWithNoNameAssignment assignment) => assignment.Attempt == next.Attempt)) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Room With No Name progress had no accepted assignment for its attempt."); } Release1RoomWithNoNameProgress release1RoomWithNoNameProgress = _state.RoomWithNoNameProgress.SingleOrDefault((Release1RoomWithNoNameProgress progress) => progress.Attempt == next.Attempt); if ((object)release1RoomWithNoNameProgress != null && ((release1RoomWithNoNameProgress.Staged && !next.Staged) || (release1RoomWithNoNameProgress.Custody && !next.Custody) || (release1RoomWithNoNameProgress.Stowed && !next.Stowed) || (release1RoomWithNoNameProgress.HoldSatisfied && !next.HoldSatisfied))) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Room With No Name progress flags cannot regress within an attempt."); } if ((object)release1RoomWithNoNameProgress != null && release1RoomWithNoNameProgress == next) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Room With No Name progress was already recorded."); } Release1StoryState release1StoryState = _state with { RoomWithNoNameProgress = (((object)release1RoomWithNoNameProgress == null) ? _state.RoomWithNoNameProgress.Append(next).ToArray() : _state.RoomWithNoNameProgress.Select((Release1RoomWithNoNameProgress progress) => (progress.Attempt != next.Attempt) ? progress : next).ToArray()), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Room With No Name progress was recorded in memory."); } } public Release1StoryRuntimeCommandResult TryExecuteShortNoticeAcceptanceDurably(Release1StoryCommand command, Release1ShortNoticeAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Short Notice assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1TransitionKind? release1TransitionKind = assignment.Mode switch { Release1ShortNoticeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1ShortNoticeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1ShortNoticeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => null, }; if (!release1TransitionKind.HasValue || command.TransitionKind != release1TransitionKind || command.MissionKey != "release1.short-notice" || assignment.MissionKey != command.MissionKey || assignment.Attempt != command.Attempt || assignment.AuthorizationCorrelationId != command.CorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Short Notice assignment did not match its acceptance command."); } if (assignment.Mode == Release1ShortNoticeAssignmentMode.Primary && !string.Equals(command.TermsVersion, "short-notice-v1", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Short Notice primary terms version was not canonical."); } Release1ShortNoticeAssignment release1ShortNoticeAssignment = _state.ShortNoticeAssignments.SingleOrDefault((Release1ShortNoticeAssignment candidate) => candidate.Attempt == assignment.Attempt); if ((object)release1ShortNoticeAssignment != null && release1ShortNoticeAssignment != assignment) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Short Notice attempt already had a different assignment."); } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted || (object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if (release1StoryTransitionResult.Idempotent) { if ((object)release1ShortNoticeAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted Short Notice correlation had no atomic assignment."); } return ReconcileAcceptedShortNotice(release1ShortNoticeAssignment); } Release1StoryState release1StoryState; try { release1StoryState = release1StoryTransitionResult.State with { ShortNoticeAssignments = release1StoryTransitionResult.State.ShortNoticeAssignments.Append(assignment).ToArray() }; release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = PersistState(release1StoryState, "Short Notice acceptance and assignment were persisted atomically."); if (!release1StoryRuntimeCommandResult2.Accepted) { return release1StoryRuntimeCommandResult2; } return ReconcileAcceptedShortNotice(assignment); } } private Release1StoryRuntimeCommandResult ReconcileAcceptedShortNotice(Release1ShortNoticeAssignment assignment) { if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; if (assignment.Mode != Release1ShortNoticeAssignmentMode.Primary || release1MissionRecord.State != Release1MissionState.Accepted) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Short Notice stage was already active."); } string receiptId = $"short-notice-activate-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.short-notice", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.short-notice", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } private Release1StoryRuntimeCommandResult? ReconcileShortNoticeOnLoad() { if ((object)_state == null) { return null; } Release1MissionRecord mission = _state.Missions[Release1MissionCatalog.IndexOf("release1.short-notice")]; if (mission.State == Release1MissionState.Accepted) { Release1ShortNoticeAssignment release1ShortNoticeAssignment = _state.ShortNoticeAssignments.SingleOrDefault((Release1ShortNoticeAssignment candidate) => candidate.Attempt == mission.Attempt && candidate.Mode == Release1ShortNoticeAssignmentMode.Primary); if ((object)release1ShortNoticeAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, $"Short Notice mission was Accepted on load but attempt {mission.Attempt} had no assignment on file."); } return ReconcileAcceptedShortNotice(release1ShortNoticeAssignment); } if (mission.State != Release1MissionState.Deferred) { return null; } string receiptId = $"short-notice-reoffer-v1-a{mission.Attempt + 1}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.short-notice", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.short-notice", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } public Release1StoryRuntimeCommandResult TrySetShortNoticeProgress(Release1ShortNoticeProgress next) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)next == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Short Notice progress was null."); } try { next.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } if (!_state.ShortNoticeAssignments.Any((Release1ShortNoticeAssignment assignment) => assignment.Attempt == next.Attempt)) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Short Notice progress had no accepted assignment for its attempt."); } Release1ShortNoticeProgress release1ShortNoticeProgress = _state.ShortNoticeProgress.SingleOrDefault((Release1ShortNoticeProgress progress) => progress.Attempt == next.Attempt); if ((object)release1ShortNoticeProgress != null && release1ShortNoticeProgress.SpreadNoticed && !next.SpreadNoticed) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Short Notice spread notice cannot be withdrawn within an attempt."); } if ((object)release1ShortNoticeProgress != null && release1ShortNoticeProgress == next) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Short Notice progress was already recorded."); } Release1StoryState release1StoryState = _state with { ShortNoticeProgress = (((object)release1ShortNoticeProgress == null) ? _state.ShortNoticeProgress.Append(next).ToArray() : _state.ShortNoticeProgress.Select((Release1ShortNoticeProgress progress) => (progress.Attempt != next.Attempt) ? progress : next).ToArray()), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Short Notice progress was recorded in memory."); } } public Release1StoryRuntimeCommandResult TryExecuteKeepTheLightsOffAcceptanceDurably(Release1StoryCommand command, Release1KeepTheLightsOffAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1TransitionKind? release1TransitionKind = assignment.Mode switch { Release1KeepTheLightsOffAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1KeepTheLightsOffAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1KeepTheLightsOffAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => null, }; if (!release1TransitionKind.HasValue || command.TransitionKind != release1TransitionKind || command.MissionKey != "release1.keep-the-lights-off" || assignment.MissionKey != command.MissionKey || assignment.Attempt != command.Attempt || assignment.AuthorizationCorrelationId != command.CorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off assignment did not match its acceptance command."); } if (assignment.Mode == Release1KeepTheLightsOffAssignmentMode.Primary && !string.Equals(command.TermsVersion, "keep-the-lights-off-v1", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off primary terms version was not canonical."); } Release1KeepTheLightsOffAssignment release1KeepTheLightsOffAssignment = _state.KeepTheLightsOffAssignments.SingleOrDefault((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == assignment.Attempt); if ((object)release1KeepTheLightsOffAssignment != null && release1KeepTheLightsOffAssignment != assignment) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off attempt already had a different assignment."); } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted || (object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if (release1StoryTransitionResult.Idempotent) { if ((object)release1KeepTheLightsOffAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted Keep the Lights Off correlation had no atomic assignment."); } return ReconcileAcceptedKeepTheLightsOff(release1KeepTheLightsOffAssignment); } Release1StoryState release1StoryState; try { release1StoryState = release1StoryTransitionResult.State with { KeepTheLightsOffAssignments = release1StoryTransitionResult.State.KeepTheLightsOffAssignments.Append(assignment).ToArray() }; release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = PersistState(release1StoryState, "Keep the Lights Off acceptance and assignment were persisted atomically."); if (!release1StoryRuntimeCommandResult2.Accepted) { return release1StoryRuntimeCommandResult2; } return ReconcileAcceptedKeepTheLightsOff(assignment); } } private Release1StoryRuntimeCommandResult ReconcileAcceptedKeepTheLightsOff(Release1KeepTheLightsOffAssignment assignment) { if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; if (assignment.Mode != Release1KeepTheLightsOffAssignmentMode.Primary || release1MissionRecord.State != Release1MissionState.Accepted) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Keep the Lights Off stage was already active."); } string receiptId = $"keep-the-lights-off-activate-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.keep-the-lights-off", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.keep-the-lights-off", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } private Release1StoryRuntimeCommandResult? ReconcileKeepTheLightsOffOnLoad() { if ((object)_state == null) { return null; } Release1MissionRecord mission = _state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; if (mission.State == Release1MissionState.Accepted) { Release1KeepTheLightsOffAssignment release1KeepTheLightsOffAssignment = _state.KeepTheLightsOffAssignments.SingleOrDefault((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == mission.Attempt && candidate.Mode == Release1KeepTheLightsOffAssignmentMode.Primary); if ((object)release1KeepTheLightsOffAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, $"Keep the Lights Off mission was Accepted on load but attempt {mission.Attempt} had no assignment on file."); } return ReconcileAcceptedKeepTheLightsOff(release1KeepTheLightsOffAssignment); } if (mission.State != Release1MissionState.Deferred) { return null; } string receiptId = $"keep-the-lights-off-reoffer-v1-a{mission.Attempt + 1}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.keep-the-lights-off", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.keep-the-lights-off", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } public Release1StoryRuntimeCommandResult TrySetKeepTheLightsOffProgress(Release1KeepTheLightsOffProgress next) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)next == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off progress was null."); } try { next.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } if (!_state.KeepTheLightsOffAssignments.Any((Release1KeepTheLightsOffAssignment assignment) => assignment.Attempt == next.Attempt)) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Keep the Lights Off progress had no accepted assignment for its attempt."); } Release1KeepTheLightsOffProgress release1KeepTheLightsOffProgress = _state.KeepTheLightsOffProgress.SingleOrDefault((Release1KeepTheLightsOffProgress progress) => progress.Attempt == next.Attempt); if ((object)release1KeepTheLightsOffProgress != null && release1KeepTheLightsOffProgress.ClearConfirmedAtGameMinutes.HasValue && !next.ClearConfirmedAtGameMinutes.HasValue) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off clear confirmation cannot be withdrawn within an attempt."); } if ((object)release1KeepTheLightsOffProgress != null && release1KeepTheLightsOffProgress == next) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Keep the Lights Off progress was already recorded."); } Release1StoryState release1StoryState = _state with { KeepTheLightsOffProgress = (((object)release1KeepTheLightsOffProgress == null) ? _state.KeepTheLightsOffProgress.Append(next).ToArray() : _state.KeepTheLightsOffProgress.Select((Release1KeepTheLightsOffProgress progress) => (progress.Attempt != next.Attempt) ? progress : next).ToArray()), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Keep the Lights Off progress was recorded in memory."); } } public Release1StoryRuntimeCommandResult TryRestoreKeepTheLightsOffAssignment(Release1KeepTheLightsOffAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")]; Release1KeepTheLightsOffAssignmentMode? release1KeepTheLightsOffAssignmentMode; switch (release1MissionRecord.State) { case Release1MissionState.Accepted: case Release1MissionState.Active: release1KeepTheLightsOffAssignmentMode = Release1KeepTheLightsOffAssignmentMode.Primary; break; case Release1MissionState.MakeGoodActive: release1KeepTheLightsOffAssignmentMode = Release1KeepTheLightsOffAssignmentMode.MakeGood; break; case Release1MissionState.RecoveryActive: release1KeepTheLightsOffAssignmentMode = Release1KeepTheLightsOffAssignmentMode.Recovery; break; default: release1KeepTheLightsOffAssignmentMode = null; break; } Release1KeepTheLightsOffAssignmentMode? release1KeepTheLightsOffAssignmentMode2 = release1KeepTheLightsOffAssignmentMode; if (!release1KeepTheLightsOffAssignmentMode2.HasValue || assignment.Mode != release1KeepTheLightsOffAssignmentMode2.Value || assignment.Attempt != release1MissionRecord.Attempt) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off restore did not match the accepted stage."); } if (!release1MissionRecord.AcceptedLogicalCorrelations.Contains(assignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Keep the Lights Off restore was not authorized by this attempt."); } if (_state.KeepTheLightsOffAssignments.Any((Release1KeepTheLightsOffAssignment candidate) => candidate.Attempt == assignment.Attempt)) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Keep the Lights Off already has an assignment for this attempt."); } Release1StoryState release1StoryState = _state with { KeepTheLightsOffAssignments = _state.KeepTheLightsOffAssignments.Append(assignment).ToArray(), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Keep the Lights Off assignment was restored."); } } public Release1StoryRuntimeCommandResult TryExecuteTheEnvelopeAcceptanceDurably(Release1StoryCommand command, Release1TheEnvelopeAssignment assignment) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckStoryCommandGate(command); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)assignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "The Envelope assignment was null."); } try { assignment.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1TransitionKind? release1TransitionKind = assignment.Mode switch { Release1TheEnvelopeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1TheEnvelopeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1TheEnvelopeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => null, }; if (!release1TransitionKind.HasValue || command.TransitionKind != release1TransitionKind || command.MissionKey != "release1.the-envelope" || assignment.MissionKey != command.MissionKey || assignment.Attempt != command.Attempt || assignment.AuthorizationCorrelationId != command.CorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "The Envelope assignment did not match its acceptance command."); } if (assignment.Mode == Release1TheEnvelopeAssignmentMode.Primary && !string.Equals(command.TermsVersion, "the-envelope-v1", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "The Envelope primary terms version was not canonical."); } Release1TheEnvelopeAssignment release1TheEnvelopeAssignment = _state.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == assignment.Attempt); if ((object)release1TheEnvelopeAssignment != null && release1TheEnvelopeAssignment != assignment) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "The Envelope attempt already had a different assignment."); } Release1StoryTransitionResult release1StoryTransitionResult = Release1StoryTransitions.Apply(_state, command); if (!release1StoryTransitionResult.Accepted || (object)release1StoryTransitionResult.State == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1StoryTransitionResult.Message); } if (release1StoryTransitionResult.Idempotent) { if ((object)release1TheEnvelopeAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Accepted The Envelope correlation had no atomic assignment."); } return ReconcileAcceptedTheEnvelope(release1TheEnvelopeAssignment); } Release1StoryState release1StoryState; try { release1StoryState = release1StoryTransitionResult.State with { TheEnvelopeAssignments = release1StoryTransitionResult.State.TheEnvelopeAssignments.Append(assignment).ToArray() }; release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult2 = PersistState(release1StoryState, "The Envelope acceptance and assignment were persisted atomically."); if (!release1StoryRuntimeCommandResult2.Accepted) { return release1StoryRuntimeCommandResult2; } return ReconcileAcceptedTheEnvelope(assignment); } } private Release1StoryRuntimeCommandResult ReconcileAcceptedTheEnvelope(Release1TheEnvelopeAssignment assignment) { if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; if (assignment.Mode != Release1TheEnvelopeAssignmentMode.Primary || release1MissionRecord.State != Release1MissionState.Accepted) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "The Envelope stage was already active."); } string receiptId = $"the-envelope-activate-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.the-envelope", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.the-envelope", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } private Release1StoryRuntimeCommandResult? ReconcileTheEnvelopeOnLoad() { if ((object)_state == null) { return null; } Release1MissionRecord mission = _state.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; if (mission.State == Release1MissionState.Accepted) { Release1TheEnvelopeAssignment release1TheEnvelopeAssignment = _state.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == mission.Attempt && candidate.Mode == Release1TheEnvelopeAssignmentMode.Primary); if ((object)release1TheEnvelopeAssignment == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, $"The Envelope mission was Accepted on load but attempt {mission.Attempt} had no assignment on file."); } return ReconcileAcceptedTheEnvelope(release1TheEnvelopeAssignment); } if (mission.State != Release1MissionState.Deferred) { return null; } string receiptId = $"the-envelope-reoffer-v1-a{mission.Attempt + 1}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.the-envelope", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.the-envelope", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } public Release1StoryRuntimeCommandResult TrySetTheEnvelopeProgress(Release1TheEnvelopeProgress next) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)next == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "The Envelope progress was null."); } try { next.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } if (!_state.TheEnvelopeAssignments.Any((Release1TheEnvelopeAssignment assignment) => assignment.Attempt == next.Attempt)) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "The Envelope progress had no accepted assignment for its attempt."); } Release1TheEnvelopeProgress release1TheEnvelopeProgress = _state.TheEnvelopeProgress.SingleOrDefault((Release1TheEnvelopeProgress progress) => progress.Attempt == next.Attempt); if ((object)release1TheEnvelopeProgress != null && release1TheEnvelopeProgress.SpreadNoticed && !next.SpreadNoticed) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "The Envelope spread notice cannot be withdrawn within an attempt."); } if ((object)release1TheEnvelopeProgress != null && release1TheEnvelopeProgress == next) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "The Envelope progress was already recorded."); } Release1StoryState release1StoryState = _state with { TheEnvelopeProgress = (((object)release1TheEnvelopeProgress == null) ? _state.TheEnvelopeProgress.Append(next).ToArray() : _state.TheEnvelopeProgress.Select((Release1TheEnvelopeProgress progress) => (progress.Attempt != next.Attempt) ? progress : next).ToArray()), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "The Envelope progress was recorded in memory."); } } public Release1StoryRuntimeCommandResult TrySetWrongAddressStaged(int attempt) { return SetWrongAddressProgress(attempt, staged: true, custody: false); } public Release1StoryRuntimeCommandResult TrySetWrongAddressCustody(int attempt) { return SetWrongAddressProgress(attempt, staged: true, custody: true); } private Release1StoryRuntimeCommandResult SetWrongAddressProgress(int attempt, bool staged, bool custody) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if (attempt < 1) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Wrong Address progress attempt was not canonical."); } if (!_state.WrongAddressAssignments.Any((Release1WrongAddressAssignment assignment) => assignment.Attempt == attempt)) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Wrong Address progress had no accepted assignment for its attempt."); } Release1WrongAddressProgress release1WrongAddressProgress = _state.WrongAddressProgress.SingleOrDefault((Release1WrongAddressProgress progress) => progress.Attempt == attempt); Release1WrongAddressProgress next = new Release1WrongAddressProgress("release1.wrong-address", attempt, staged || (release1WrongAddressProgress?.Staged ?? false), custody || (release1WrongAddressProgress?.Custody ?? false)); if ((object)release1WrongAddressProgress != null && release1WrongAddressProgress == next) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Wrong Address progress was already recorded."); } Release1StoryState release1StoryState = _state with { WrongAddressProgress = (((object)release1WrongAddressProgress == null) ? _state.WrongAddressProgress.Append(next).ToArray() : _state.WrongAddressProgress.Select((Release1WrongAddressProgress progress) => (progress.Attempt != attempt) ? progress : next).ToArray()), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Wrong Address progress was recorded in memory."); } } private Release1StoryRuntimeCommandResult? CheckStoryCommandGate(Release1StoryCommand? command) { if (_disposed) { return Reject(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } if (_phase == Release1StoryRuntimePhase.Quarantined) { return Reject(Release1StoryRuntimeRejectReason.Quarantined, "Story runtime is quarantined."); } if (_phase == Release1StoryRuntimePhase.Saving) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.DeferredSaving, Release1StoryRuntimeRejectReason.None, _state, "Story command was deferred during saving."); } if (_phase != Release1StoryRuntimePhase.Active) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story runtime is not active."); } Release1StoryHostContextReadStatus release1StoryHostContextReadStatus; Release1StoryHostContextSnapshot snapshot; try { release1StoryHostContextReadStatus = _context.TryRead(out snapshot); } catch (Exception ex) { return Reject(Release1StoryRuntimeRejectReason.ContextFaulted, ex.Message); } if (release1StoryHostContextReadStatus != Release1StoryHostContextReadStatus.Ready) { return Reject(Map(release1StoryHostContextReadStatus), "Host context was not ready."); } if ((object)command == null || snapshot.SessionEpoch != _activeContext.SessionEpoch || snapshot.LoadEpoch != _activeContext.LoadEpoch || command.SessionEpoch != _activeContext.SessionEpoch || command.LoadEpoch != _activeContext.LoadEpoch) { return Reject(Release1StoryRuntimeRejectReason.WrongEpoch, "Story command epoch was stale."); } if (snapshot.PlayerId != _activeContext.PlayerId || command.PlayerId != _activeContext.PlayerId) { return Reject(Release1StoryRuntimeRejectReason.IdentityMismatch, "Story command identity did not match host context."); } return null; } private Release1StoryRuntimeCommandResult ReconcileAcceptedSmallCourtesy(Release1SmallCourtesyAssignment assignment) { if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1MissionRecord release1MissionRecord = _state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; if (assignment.Mode != Release1SmallCourtesyAssignmentMode.Primary || release1MissionRecord.State != Release1MissionState.Accepted) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Small Courtesy stage was already active."); } string receiptId = $"small-courtesy-activate-v1-a{release1MissionRecord.Attempt}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.small-courtesy", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.small-courtesy", release1MissionRecord.Attempt, Release1TransitionKind.MissionActivated, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } private Release1StoryRuntimeCommandResult? ReconcileSmallCourtesyOnLoad() { if ((object)_state == null) { return null; } Release1MissionRecord mission = _state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")]; if (mission.State == Release1MissionState.Accepted) { Release1SmallCourtesyAssignment release1SmallCourtesyAssignment = _state.SmallCourtesyAssignments.SingleOrDefault((Release1SmallCourtesyAssignment candidate) => candidate.Attempt == mission.Attempt && candidate.Mode == Release1SmallCourtesyAssignmentMode.Primary); if ((object)release1SmallCourtesyAssignment != null) { return ReconcileAcceptedSmallCourtesy(release1SmallCourtesyAssignment); } return null; } if (mission.State != Release1MissionState.Deferred) { return null; } string receiptId = $"small-courtesy-reoffer-v1-a{mission.Attempt + 1}"; Release1StoryCommand command = new Release1StoryCommand(_activeContext.SessionEpoch, _activeContext.LoadEpoch, _activeContext.PlayerId, "release1.small-courtesy", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId, Release1LogicalCorrelation.Create(_activeContext.PlayerId, "release1.small-courtesy", mission.Attempt, Release1TransitionKind.MissionReoffered, receiptId).Value); return TryExecuteCore(command, persistImmediately: true); } public Release1StoryRuntimeCommandResult TryAuthorizePhonePresentation(string missionKey, int attempt, string correlationId, string role, string? requiredPriorCorrelationId) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if (!Release1MissionCatalog.IsMissionKey(missionKey) || attempt < 1) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Phone presentation mission or attempt was not canonical."); } if (!Release1LogicalCorrelation.TryParse(correlationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != _state.PlayerId || correlation.MissionKey != missionKey || correlation.Attempt != attempt) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Phone presentation correlation did not match the story identity, mission, or attempt."); } if ((!(role == "Nell") && !(role == "Arthur")) || 1 == 0) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Phone presentation role was invalid."); } if ((object)_state.PhonePresentationAttempts.FirstOrDefault((Release1PhonePresentationAttempt candidate) => candidate.CorrelationId == correlationId) != null) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Phone presentation authorization already exists."); } if ((object)_state.Missions.SingleOrDefault((Release1MissionRecord candidate) => candidate.MissionKey == missionKey && candidate.Attempt == attempt) == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Phone presentation attempt did not match the story state."); } bool flag = !string.IsNullOrWhiteSpace(requiredPriorCorrelationId) && _state.PhonePresentationAttempts.Any((Release1PhonePresentationAttempt candidate) => candidate.CorrelationId == requiredPriorCorrelationId && candidate.Role == "Nell" && candidate.State != Release1PhonePresentationAttemptState.Ambiguous); Release1LogicalCorrelation correlation2; bool flag2 = !string.IsNullOrWhiteSpace(requiredPriorCorrelationId) && Release1LogicalCorrelation.TryParse(requiredPriorCorrelationId, out correlation2) && correlation2.PlayerId == _state.PlayerId && correlation2.MissionKey == missionKey && correlation2.Attempt == attempt && correlation2.ReceiptId.StartsWith("presentation-nell-", StringComparison.Ordinal) && _state.PresentationReceipts.Any((Release1PresentationReceipt receipt) => receipt.CorrelationId == requiredPriorCorrelationId); if (role == "Arthur" && !flag && !flag2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Arthur requires a delivered Nell call or a receipted Nell message on this mission and attempt."); } if (role == "Nell" && requiredPriorCorrelationId != null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Nell cannot have a prior presentation."); } Release1PhonePresentationAttempt release1PhonePresentationAttempt = new Release1PhonePresentationAttempt(correlationId, missionKey, attempt, role, requiredPriorCorrelationId, Release1PhonePresentationAttemptState.Pending, _state.Revision + 1); try { release1PhonePresentationAttempt.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1StoryState updated = _state with { PhonePresentationAttempts = _state.PhonePresentationAttempts.Append(release1PhonePresentationAttempt).ToArray(), Revision = _state.Revision + 1 }; return PersistState(updated, "Phone presentation authorization was persisted."); } } public Release1StoryRuntimeCommandResult TryTransitionPhonePresentation(string correlationId, Release1PhonePresentationAttemptState nextState) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1PhonePresentationAttempt release1PhonePresentationAttempt = _state.PhonePresentationAttempts.SingleOrDefault((Release1PhonePresentationAttempt candidate) => candidate.CorrelationId == correlationId); if ((object)release1PhonePresentationAttempt == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Phone presentation correlation was not authorized."); } if (!Enum.IsDefined(nextState)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Phone presentation state was invalid."); } if (release1PhonePresentationAttempt.State == nextState) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Phone presentation state was already recorded."); } if (!IsAllowedPhonePresentationTransition(release1PhonePresentationAttempt.State, nextState)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Phone presentation state transition was not allowed."); } Release1PhonePresentationAttempt updatedAttempt = release1PhonePresentationAttempt with { State = nextState, Revision = _state.Revision + 1 }; Release1StoryState updated = _state with { PhonePresentationAttempts = _state.PhonePresentationAttempts.Select((Release1PhonePresentationAttempt candidate) => (!(candidate.CorrelationId == correlationId)) ? candidate : updatedAttempt).ToArray(), Revision = _state.Revision + 1 }; return PersistState(updated, "Phone presentation state was persisted."); } } public Release1StoryRuntimeCommandResult TryRecordPresentationReceipt(string correlationId) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if (!Release1LogicalCorrelation.TryParse(correlationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != _state.PlayerId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Presentation receipt correlation did not match the story identity."); } if ((object)_state.PresentationReceipts.FirstOrDefault((Release1PresentationReceipt candidate) => candidate.CorrelationId == correlationId) != null) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Presentation receipt already exists."); } Release1PresentationReceipt release1PresentationReceipt = new Release1PresentationReceipt(correlationId, _state.Revision + 1); release1PresentationReceipt.Validate(); Release1StoryState release1StoryState = _state with { PresentationReceipts = _state.PresentationReceipts.Append(release1PresentationReceipt).ToArray(), Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Presentation receipt was recorded in memory."); } } public Release1PhonePresentationAttempt? GetPhonePresentationAttempt(string correlationId) { lock (_gate) { return _state?.PhonePresentationAttempts.SingleOrDefault((Release1PhonePresentationAttempt candidate) => candidate.CorrelationId == correlationId); } } public IReadOnlyList GetPhonePresentationAttempts() { lock (_gate) { return _state?.PhonePresentationAttempts.ToArray() ?? Array.Empty(); } } public Release1StoryRuntimeCommandResult TrySetChiefRecord(Release1ChiefRecord next) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckPresentationMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if ((object)next == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Chief record was null."); } try { next.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } Release1ChiefRecord chiefRecord = _state.ChiefRecord; if ((object)chiefRecord != null && chiefRecord.ValueEquals(next)) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.None, _state, "Chief record was already recorded."); } long num = (((object)chiefRecord == null) ? 0 : (chiefRecord.Revision + 1)); if (next.Revision != num) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Chief record revision did not advance by exactly one."); } bool flag = (object)chiefRecord != null && chiefRecord.State == Release1ChiefState.Settled && next.State == Release1ChiefState.DemandOpen && next.DemandRound == 1; if ((object)chiefRecord != null && next.DemandRound < chiefRecord.DemandRound && !flag) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Chief demand round cannot fall."); } Release1StoryState release1StoryState = _state with { ChiefRecord = next, Revision = _state.Revision + 1 }; try { release1StoryState.Validate(); } catch (ArgumentException ex2) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex2.Message); } _state = release1StoryState; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, "Chief record was recorded in memory."); } } public bool TryGetActiveContext(out Release1StoryHostContextSnapshot snapshot, out Release1StoryRuntimeRejectReason rejectReason) { lock (_gate) { snapshot = default(Release1StoryHostContextSnapshot); rejectReason = Release1StoryRuntimeRejectReason.None; if (_disposed) { rejectReason = Release1StoryRuntimeRejectReason.Disposed; return false; } if (_phase == Release1StoryRuntimePhase.Quarantined) { rejectReason = Release1StoryRuntimeRejectReason.Quarantined; return false; } if (_phase != Release1StoryRuntimePhase.Active) { rejectReason = Release1StoryRuntimeRejectReason.Inactive; return false; } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckCurrentAuthority(); if ((object)release1StoryRuntimeCommandResult != null) { rejectReason = release1StoryRuntimeCommandResult.RejectReason; return false; } snapshot = _activeContext; return true; } } private Release1StoryRuntimeCommandResult? CheckPresentationMutationGate() { if (_disposed) { return Reject(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } if (_phase == Release1StoryRuntimePhase.Quarantined) { return Reject(Release1StoryRuntimeRejectReason.Quarantined, "Story runtime is quarantined."); } if (_phase == Release1StoryRuntimePhase.Saving) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.DeferredSaving, Release1StoryRuntimeRejectReason.None, _state, "Phone presentation mutation was deferred during saving."); } if (_phase != Release1StoryRuntimePhase.Active) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story runtime is not active."); } return CheckCurrentAuthority(); } private Release1StoryRuntimeCommandResult PersistState(Release1StoryState updated, string successMessage) { if ((object)_state != null && _state.Revision > LastPersistedRevision && _state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.Phase == Release1NativeEffectPhase.Applied)) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.DeferredSaving, Release1StoryRuntimeRejectReason.None, _state, "Immediate persistence was deferred until the native save covers the applied effect."); } try { Release1StoryStoreUpdateResult release1StoryStoreUpdateResult = Timing.Measure("story-sidecar-write/transition", () => _repository.Update(updated)); if (!release1StoryStoreUpdateResult.Succeeded) { return Reject(Release1StoryRuntimeRejectReason.RepositoryFaulted, release1StoryStoreUpdateResult.Message); } _state = updated; LastPersistedRevision = updated.Revision; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, successMessage); } catch (Exception ex) { return Reject(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex.Message); } } private void RecoverAttemptingPhonePresentations() { if ((object)_state == null) { return; } Release1PhonePresentationAttempt[] array = _state.PhonePresentationAttempts.Select((Release1PhonePresentationAttempt attempt) => (attempt.State != Release1PhonePresentationAttemptState.Attempting) ? attempt : attempt with { State = Release1PhonePresentationAttemptState.Ambiguous, Revision = _state.Revision + 1 }).ToArray(); if (array.SequenceEqual(_state.PhonePresentationAttempts)) { return; } Release1StoryState release1StoryState = (_state = _state with { PhonePresentationAttempts = array, Revision = _state.Revision + 1 }); try { if (_repository.Update(release1StoryState).Succeeded) { LastPersistedRevision = release1StoryState.Revision; } } catch { } } private static bool IsAllowedPhonePresentationTransition(Release1PhonePresentationAttemptState current, Release1PhonePresentationAttemptState next) { switch (current) { case Release1PhonePresentationAttemptState.Pending: if (next != Release1PhonePresentationAttemptState.Attempting) { break; } return true; case Release1PhonePresentationAttemptState.Attempting: switch (next) { case Release1PhonePresentationAttemptState.Delivered: return true; case Release1PhonePresentationAttemptState.Ambiguous: return true; } break; case Release1PhonePresentationAttemptState.Delivered: if (next != Release1PhonePresentationAttemptState.Completed) { break; } return true; } return false; } public Release1StoryRuntimeCommandResult TryPrepareNativeEffect(Release1NativeEffectJournalEntry effect) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckEffectMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)effect == null) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect request was null."); } try { effect.Validate(); } catch (ArgumentException ex) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, ex.Message); } if (effect.Phase != Release1NativeEffectPhase.Prepared) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Only Prepared effects may be issued for native execution."); } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if (string.Equals(effect.MissionKey, "release1.chief-campbell", StringComparison.Ordinal)) { return PrepareChiefEffect(effect); } Release1LogicalCorrelation correlation; bool flag = !Release1LogicalCorrelation.TryParse(effect.AuthorizedStoryCorrelationId, out correlation) || correlation.PlayerId != _state.PlayerId || correlation.MissionKey != effect.MissionKey || correlation.Attempt != effect.Attempt; if (!flag) { Release1TransitionKind transitionKind = correlation.TransitionKind; bool flag2 = ((transitionKind == Release1TransitionKind.MissionActivated || transitionKind == Release1TransitionKind.MakeGoodAccepted || transitionKind == Release1TransitionKind.RecoveryAccepted) ? true : false); flag = !flag2; } if (flag) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect did not reference an eligible accepted story transition."); } Release1MissionRecord mission = _state.Missions.FirstOrDefault((Release1MissionRecord m) => m.MissionKey == effect.MissionKey && m.Attempt == effect.Attempt); if ((object)mission == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Effect mission/attempt was not active in story state."); } if (!mission.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal) || (correlation.TransitionKind == Release1TransitionKind.MissionActivated && mission.State != Release1MissionState.Active) || (correlation.TransitionKind == Release1TransitionKind.MakeGoodAccepted && mission.State != Release1MissionState.MakeGoodActive) || (correlation.TransitionKind == Release1TransitionKind.RecoveryAccepted && mission.State != Release1MissionState.RecoveryActive) || (effect.AuthorizedMissionRevision >= 0 && effect.AuthorizedMissionRevision != mission.Revision) || string.Equals(effect.EffectKind, "Reward", StringComparison.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect authorization did not match the mission state or effect type."); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effect.EffectId); if ((object)release1NativeEffectJournalEntry == null && effect.PreparedStoryRevision != _state.Revision + 1) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Prepared effect revision did not match the next story revision."); } if ((object)release1NativeEffectJournalEntry != null && !IsCurrentEffectAuthorization(release1NativeEffectJournalEntry)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect authorization was superseded by a later mission transition."); } Release1NativeEffectTransitionResult release1NativeEffectTransitionResult = Release1NativeEffectJournal.Apply(_state.NativeEffects, new Release1NativeEffectCommand(Release1NativeEffectCommandKind.Prepare, effect.EffectId, effect.MissionKey, effect.Attempt, effect.EffectKind, effect.SourceIdentity, effect.DestinationIdentity, effect.AmountOrCargoIdentity, null, effect.AuthorizedStoryCorrelationId, mission.Revision + 1), _state.Revision + 1); if (!release1NativeEffectTransitionResult.Accepted) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1NativeEffectTransitionResult.Message); } if (!release1NativeEffectTransitionResult.Idempotent) { Release1MissionRecord[] missions = _state.Missions.Select((Release1MissionRecord m) => (!(m.MissionKey == mission.MissionKey)) ? m : m with { NativeEffectIds = m.NativeEffectIds.Append(effect.EffectId).ToArray(), Revision = m.Revision + 1 }).ToArray(); _state = _state with { NativeEffects = release1NativeEffectTransitionResult.Effects, Missions = missions, Revision = _state.Revision + 1 }; } return new Release1StoryRuntimeCommandResult(release1NativeEffectTransitionResult.Idempotent ? Release1StoryCommandStatus.NoOp : Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, release1NativeEffectTransitionResult.Message); } } private Release1StoryRuntimeCommandResult PrepareChiefEffect(Release1NativeEffectJournalEntry effect) { Release1ChiefRecord chiefRecord = _state.ChiefRecord; if ((object)chiefRecord == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Chief record was absent."); } bool flag = effect.Attempt != chiefRecord.DemandRound; if (!flag) { Release1ChiefState state = chiefRecord.State; bool flag2 = (uint)(state - 1) <= 1u; flag = !flag2; } if (flag) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Chief effect did not match a payable round."); } if (!Release1LogicalCorrelation.TryParse(effect.AuthorizedStoryCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != _state.PlayerId || correlation.MissionKey != effect.MissionKey || correlation.Attempt != effect.Attempt || correlation.TransitionKind != Release1TransitionKind.ChiefPaymentAccepted || !string.Equals(effect.EffectKind, "CashTransfer", StringComparison.Ordinal) || (effect.AuthorizedMissionRevision >= 0 && effect.AuthorizedMissionRevision != chiefRecord.Revision)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Chief effect authorization did not match his record."); } if (chiefRecord.State == Release1ChiefState.Paying && !chiefRecord.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Chief effect authorization did not match his record."); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effect.EffectId); if ((object)release1NativeEffectJournalEntry == null && effect.PreparedStoryRevision != _state.Revision + 1) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Prepared effect revision did not match the next story revision."); } if ((object)release1NativeEffectJournalEntry != null && !IsCurrentEffectAuthorization(release1NativeEffectJournalEntry)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect authorization was superseded."); } Release1NativeEffectTransitionResult release1NativeEffectTransitionResult = Release1NativeEffectJournal.Apply(_state.NativeEffects, new Release1NativeEffectCommand(Release1NativeEffectCommandKind.Prepare, effect.EffectId, effect.MissionKey, effect.Attempt, effect.EffectKind, effect.SourceIdentity, effect.DestinationIdentity, effect.AmountOrCargoIdentity, null, effect.AuthorizedStoryCorrelationId, chiefRecord.Revision + 1), _state.Revision + 1); if (!release1NativeEffectTransitionResult.Accepted) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1NativeEffectTransitionResult.Message); } if (!release1NativeEffectTransitionResult.Idempotent) { object obj; if (chiefRecord.State != Release1ChiefState.Paying) { obj = chiefRecord with { State = Release1ChiefState.Paying, LastShortfallNoticed = null }; object obj2 = obj; IReadOnlyList acceptedLogicalCorrelations; if (!chiefRecord.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal)) { IReadOnlyList readOnlyList = chiefRecord.AcceptedLogicalCorrelations.Append(effect.AuthorizedStoryCorrelationId).ToArray(); acceptedLogicalCorrelations = readOnlyList; } else { acceptedLogicalCorrelations = chiefRecord.AcceptedLogicalCorrelations; } ((Release1ChiefRecord)obj2).AcceptedLogicalCorrelations = acceptedLogicalCorrelations; object obj3 = obj; IReadOnlyList nativeEffectIds; if (!chiefRecord.NativeEffectIds.Contains(effect.EffectId, StringComparer.Ordinal)) { IReadOnlyList readOnlyList = chiefRecord.NativeEffectIds.Append(effect.EffectId).ToArray(); nativeEffectIds = readOnlyList; } else { nativeEffectIds = chiefRecord.NativeEffectIds; } ((Release1ChiefRecord)obj3).NativeEffectIds = nativeEffectIds; ((Release1ChiefRecord)obj).Revision = chiefRecord.Revision + 1; } else { obj = chiefRecord with { Revision = chiefRecord.Revision + 1 }; } Release1ChiefRecord chiefRecord2 = (Release1ChiefRecord)obj; _state = _state with { NativeEffects = release1NativeEffectTransitionResult.Effects, ChiefRecord = chiefRecord2, Revision = _state.Revision + 1 }; } return new Release1StoryRuntimeCommandResult(release1NativeEffectTransitionResult.Idempotent ? Release1StoryCommandStatus.NoOp : Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, release1NativeEffectTransitionResult.Message); } public Release1StoryRuntimeCommandResult TryMarkNativeEffectApplied(string effectId, string nativeReceiptId, Release1NativeEffectPersistenceMode mode = Release1NativeEffectPersistenceMode.SaveGated) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckEffectMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } if (!Enum.IsDefined(mode)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Native effect persistence mode was not defined."); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effectId); if ((object)release1NativeEffectJournalEntry == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Effect was not found."); } if (!IsCurrentEffectAuthorization(release1NativeEffectJournalEntry)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect authorization was superseded by a later mission transition."); } if (mode == Release1NativeEffectPersistenceMode.RevertTolerant && !Release1MissionCatalog.AllowsRevertTolerantEffects(release1NativeEffectJournalEntry.MissionKey)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Only revert-tolerant missions may use the revert-tolerant mode."); } if (mode == Release1NativeEffectPersistenceMode.SaveGated && release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared && release1NativeEffectJournalEntry.PreparedStoryRevision > LastPersistedRevision) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Prepared effect was not durably persisted before native application."); } Release1NativeEffectTransitionResult release1NativeEffectTransitionResult = Release1NativeEffectJournal.Apply(_state.NativeEffects, new Release1NativeEffectCommand(Release1NativeEffectCommandKind.MarkApplied, effectId, release1NativeEffectJournalEntry.MissionKey, release1NativeEffectJournalEntry.Attempt, release1NativeEffectJournalEntry.EffectKind, release1NativeEffectJournalEntry.SourceIdentity, release1NativeEffectJournalEntry.DestinationIdentity, release1NativeEffectJournalEntry.AmountOrCargoIdentity, nativeReceiptId, null, -1L), _state.Revision + 1); if (!release1NativeEffectTransitionResult.Accepted) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1NativeEffectTransitionResult.Message); } if (!release1NativeEffectTransitionResult.Idempotent) { _state = _state with { NativeEffects = release1NativeEffectTransitionResult.Effects, Revision = _state.Revision + 1 }; } return new Release1StoryRuntimeCommandResult(release1NativeEffectTransitionResult.Idempotent ? Release1StoryCommandStatus.NoOp : Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, release1NativeEffectTransitionResult.Message); } } public Release1StoryRuntimeCommandResult TryCommitNativeEffect(string effectId, string storyCorrelationId, long? capturedPersistedRevision = null) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckEffectMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effectId); if ((object)release1NativeEffectJournalEntry == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Effect was not found."); } if (!IsCurrentEffectAuthorization(release1NativeEffectJournalEntry)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect authorization was superseded by a later mission transition."); } if (capturedPersistedRevision.HasValue && !Release1MissionCatalog.AllowsRevertTolerantEffects(release1NativeEffectJournalEntry.MissionKey)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Only revert-tolerant missions may commit against a captured revision."); } long num = capturedPersistedRevision ?? _state.Revision; if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied && num > LastPersistedRevision) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Applied effect was not durably persisted with its native mutation before commit."); } if (release1NativeEffectJournalEntry.PreparedStoryRevision > LastPersistedRevision || release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId == null || release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId != storyCorrelationId) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect commit did not reference its durable authorization correlation."); } if (!Release1LogicalCorrelation.TryParse(storyCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != _state.PlayerId || correlation.MissionKey != release1NativeEffectJournalEntry.MissionKey || correlation.Attempt != release1NativeEffectJournalEntry.Attempt) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect commit correlation did not match the effect."); } Release1NativeEffectTransitionResult release1NativeEffectTransitionResult = Release1NativeEffectJournal.Apply(_state.NativeEffects, new Release1NativeEffectCommand(Release1NativeEffectCommandKind.Commit, effectId, release1NativeEffectJournalEntry.MissionKey, release1NativeEffectJournalEntry.Attempt, release1NativeEffectJournalEntry.EffectKind, release1NativeEffectJournalEntry.SourceIdentity, release1NativeEffectJournalEntry.DestinationIdentity, release1NativeEffectJournalEntry.AmountOrCargoIdentity, release1NativeEffectJournalEntry.NativeReceiptId, storyCorrelationId, -1L), _state.Revision + 1); if (!release1NativeEffectTransitionResult.Accepted) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, release1NativeEffectTransitionResult.Message); } if (!release1NativeEffectTransitionResult.Idempotent) { _state = _state with { NativeEffects = release1NativeEffectTransitionResult.Effects, Revision = _state.Revision + 1 }; } return new Release1StoryRuntimeCommandResult(release1NativeEffectTransitionResult.Idempotent ? Release1StoryCommandStatus.NoOp : Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.None, _state, release1NativeEffectTransitionResult.Message); } } public Release1StoryRuntimeCommandResult TryRecordNativeEffectIssuance(string effectId, Release1NativeEffectIssuanceOutcome outcome, string? nativeReceiptId = null) { lock (_gate) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckEffectMutationGate(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if (outcome == Release1NativeEffectIssuanceOutcome.Applied) { return TryMarkNativeEffectApplied(effectId, nativeReceiptId ?? string.Empty); } if ((object)_state == null) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story state was absent."); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effectId); if ((object)release1NativeEffectJournalEntry == null) { return Reject(Release1StoryRuntimeRejectReason.EffectNotFound, "Effect was not found."); } if (outcome == Release1NativeEffectIssuanceOutcome.Ambiguous) { if (!IsCurrentEffectAuthorization(release1NativeEffectJournalEntry)) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect authorization was superseded by a later mission transition."); } if (release1NativeEffectJournalEntry.ExecutionBlocked) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.NoOp, Release1StoryRuntimeRejectReason.EffectAmbiguous, _state, "Ambiguous issuance was already recorded."); } if (release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Prepared) { return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Only a prepared effect may be marked ambiguous."); } Release1NativeEffectJournalEntry[] nativeEffects = _state.NativeEffects.Select((Release1NativeEffectJournalEntry e) => (!(e.EffectId == effectId)) ? e : e with { ExecutionBlocked = true }).ToArray(); _state = _state with { NativeEffects = nativeEffects, Revision = _state.Revision + 1 }; return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Accepted, Release1StoryRuntimeRejectReason.EffectAmbiguous, _state, "Ambiguous issuance is blocked pending authoritative reconciliation."); } return Reject(Release1StoryRuntimeRejectReason.InvalidTransition, "Effect issuance failed; no native mutation was authorized."); } } public bool TryGetExecutablePreparedEffect(string effectId, out Release1NativeEffectJournalEntry effect) { bool flag; lock (_gate) { effect = null; Release1StoryRuntimePhase phase = _phase; flag = (uint)(phase - 2) <= 1u; if (!flag || (object)_state == null || (object)CheckCurrentAuthority() != null) { flag = false; } else { Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == effectId); if ((object)release1NativeEffectJournalEntry == null || !IsCurrentEffectAuthorization(release1NativeEffectJournalEntry) || release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Prepared || release1NativeEffectJournalEntry.ExecutionBlocked || release1NativeEffectJournalEntry.PreparedStoryRevision > LastPersistedRevision) { flag = false; } else { effect = release1NativeEffectJournalEntry; flag = true; } } } return flag; } public void Dispose() { lock (_gate) { if (!_disposed) { _disposed = true; _state = null; _saveSnapshot = null; _phase = Release1StoryRuntimePhase.Disposed; } } } private Release1StoryRuntimeCommandResult? CheckEffectMutationGate() { if (_disposed) { return Reject(Release1StoryRuntimeRejectReason.Disposed, "Service was disposed."); } if (_phase == Release1StoryRuntimePhase.Quarantined) { return Reject(Release1StoryRuntimeRejectReason.Quarantined, "Story runtime is quarantined."); } Release1StoryRuntimePhase phase = _phase; if ((uint)(phase - 2) > 1u) { return Reject(Release1StoryRuntimeRejectReason.Inactive, "Story runtime is not active."); } Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = CheckCurrentAuthority(); if ((object)release1StoryRuntimeCommandResult != null) { return release1StoryRuntimeCommandResult; } if (_phase == Release1StoryRuntimePhase.Saving) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.DeferredSaving, Release1StoryRuntimeRejectReason.None, _state, "Story effect command was deferred during saving."); } return null; } private Release1StoryRuntimeCommandResult? CheckCurrentAuthority() { Release1StoryHostContextReadStatus release1StoryHostContextReadStatus; Release1StoryHostContextSnapshot snapshot; try { release1StoryHostContextReadStatus = _context.TryRead(out snapshot); } catch (Exception ex) { return Reject(Release1StoryRuntimeRejectReason.ContextFaulted, ex.Message); } if (release1StoryHostContextReadStatus != Release1StoryHostContextReadStatus.Ready) { return Reject(Map(release1StoryHostContextReadStatus), "Host context was not ready."); } if (snapshot.SessionEpoch != _activeContext.SessionEpoch || snapshot.LoadEpoch != _activeContext.LoadEpoch) { return Reject(Release1StoryRuntimeRejectReason.WrongEpoch, "Host context epoch was stale."); } if (snapshot.PlayerId != _activeContext.PlayerId || string.IsNullOrWhiteSpace(snapshot.PlayerId)) { return Reject(Release1StoryRuntimeRejectReason.IdentityMismatch, "Host context identity did not match."); } try { if (!SameSaveFolder(snapshot.ActiveSaveFolder, _activeContext.ActiveSaveFolder) || !(_repository is IRelease1StorySaveFolderBoundRepository release1StorySaveFolderBoundRepository) || !SameSaveFolder(release1StorySaveFolderBoundRepository.BoundSaveFolder, snapshot.ActiveSaveFolder)) { return Reject(Release1StoryRuntimeRejectReason.RepositoryPathMismatch, "Host and repository save folders did not match."); } } catch (Exception ex2) { return Reject(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex2.Message); } return null; } private bool IsCurrentEffectAuthorization(Release1NativeEffectJournalEntry effect) { if ((object)_state == null || effect.AuthorizedStoryCorrelationId == null || !Release1LogicalCorrelation.TryParse(effect.AuthorizedStoryCorrelationId, out Release1LogicalCorrelation correlation)) { return false; } bool flag2; bool flag3; if (string.Equals(effect.MissionKey, "release1.chief-campbell", StringComparison.Ordinal)) { Release1ChiefRecord chiefRecord = _state.ChiefRecord; if ((object)chiefRecord == null) { return false; } if (effect.Phase == Release1NativeEffectPhase.Applied && !effect.ExecutionBlocked) { if (correlation.PlayerId == _state.PlayerId && correlation.MissionKey == "release1.chief-campbell") { return correlation.TransitionKind == Release1TransitionKind.ChiefPaymentAccepted; } return false; } if (effect.Attempt != chiefRecord.DemandRound) { return false; } bool flag = chiefRecord.State == Release1ChiefState.Settled && chiefRecord.Revision > 0 && effect.AuthorizedMissionRevision == chiefRecord.Revision - 1; if (effect.AuthorizedMissionRevision != chiefRecord.Revision && !flag) { return false; } flag2 = chiefRecord.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal) && correlation.PlayerId == _state.PlayerId && correlation.MissionKey == "release1.chief-campbell" && correlation.Attempt == chiefRecord.DemandRound && correlation.TransitionKind == Release1TransitionKind.ChiefPaymentAccepted; if (flag2) { Release1ChiefState state = chiefRecord.State; flag3 = (uint)(state - 2) <= 1u; flag2 = flag3; } return flag2; } Release1MissionRecord release1MissionRecord = _state.Missions.SingleOrDefault((Release1MissionRecord m) => m.MissionKey == effect.MissionKey && m.Attempt == effect.Attempt); if ((object)release1MissionRecord == null || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal)) { return false; } string effectKind = effect.EffectKind; flag2 = ((effectKind == "CargoTransfer" || effectKind == "CashTransfer") ? true : false); flag3 = flag2; if (flag3) { Release1NativeEffectPhase phase = effect.Phase; bool flag4 = (uint)(phase - 1) <= 1u; flag3 = flag4; } bool flag5 = flag3 && release1MissionRecord.State == Release1MissionState.Satisfied && release1MissionRecord.Revision > 0 && effect.AuthorizedMissionRevision == release1MissionRecord.Revision - 1; if (flag5) { Release1TransitionKind transitionKind = correlation.TransitionKind; bool flag4 = ((transitionKind == Release1TransitionKind.MissionActivated || transitionKind == Release1TransitionKind.MakeGoodAccepted || transitionKind == Release1TransitionKind.RecoveryAccepted) ? true : false); flag5 = flag4; } bool flag6 = flag5; if (release1MissionRecord.Revision != effect.AuthorizedMissionRevision && !flag6) { return false; } flag5 = correlation.PlayerId == _state.PlayerId && correlation.MissionKey == release1MissionRecord.MissionKey && correlation.Attempt == release1MissionRecord.Attempt; if (flag5) { flag5 = correlation.TransitionKind switch { Release1TransitionKind.MissionAccepted => release1MissionRecord.State == Release1MissionState.Accepted, Release1TransitionKind.MissionActivated => release1MissionRecord.State == Release1MissionState.Active || flag6, Release1TransitionKind.MissionCompleted => release1MissionRecord.State == Release1MissionState.Satisfied, Release1TransitionKind.MakeGoodAccepted => release1MissionRecord.State == Release1MissionState.MakeGoodActive || flag6, Release1TransitionKind.RecoveryAccepted => release1MissionRecord.State == Release1MissionState.RecoveryActive || flag6, _ => false, }; } return flag5; } private static bool SameSaveFolder(string left, string right) { return string.Equals(Normalize(left), Normalize(right), StringComparison.OrdinalIgnoreCase); static string Normalize(string value) { string fullPath = Path.GetFullPath(value.Trim()); string pathRoot = Path.GetPathRoot(fullPath); if (pathRoot == null || fullPath.Length <= pathRoot.Length) { return fullPath; } return fullPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); } } private Release1StoryRuntimeLifecycleResult PreserveQuarantinedSidecar() { if (_quarantinedRawContents == null || !(_repository is IRelease1StoryRawContentPreservingRepository release1StoryRawContentPreservingRepository)) { return Lifecycle(Release1StoryRuntimeRejectReason.Quarantined, "Story runtime is quarantined; no sidecar bytes were available to preserve."); } Release1StoryStoreUpdateResult release1StoryStoreUpdateResult; try { release1StoryStoreUpdateResult = release1StoryRawContentPreservingRepository.PreserveRawContents(_quarantinedRawContents); } catch (Exception ex) { return Lifecycle(Release1StoryRuntimeRejectReason.RepositoryFaulted, ex.Message); } if (!release1StoryStoreUpdateResult.Succeeded) { return Lifecycle(Release1StoryRuntimeRejectReason.SidecarSaveFailed, release1StoryStoreUpdateResult.Message); } return Lifecycle(Release1StoryRuntimeRejectReason.Quarantined, "Story runtime is quarantined; quarantined sidecar bytes were preserved."); } private Release1StoryRuntimeLifecycleResult Lifecycle(Release1StoryRuntimeRejectReason reason, string message) { _lastLifecycleRejectReason = reason; return new Release1StoryRuntimeLifecycleResult(_phase, reason, message); } private Release1StoryRuntimeCommandResult Reject(Release1StoryRuntimeRejectReason reason, string message) { return new Release1StoryRuntimeCommandResult(Release1StoryCommandStatus.Rejected, reason, _state, message); } private static Release1StoryRuntimeRejectReason Map(Release1StoryHostContextReadStatus status) { return status switch { Release1StoryHostContextReadStatus.Pending => Release1StoryRuntimeRejectReason.ContextPending, Release1StoryHostContextReadStatus.NotAuthoritative => Release1StoryRuntimeRejectReason.NotAuthoritative, Release1StoryHostContextReadStatus.UnsupportedMultiplayer => Release1StoryRuntimeRejectReason.UnsupportedMultiplayer, Release1StoryHostContextReadStatus.AmbiguousIdentity => Release1StoryRuntimeRejectReason.AmbiguousIdentity, _ => Release1StoryRuntimeRejectReason.ContextFaulted, }; } } public static class Release1TheEnvelopeCashGateHarness { public const float DecrementAmount = 100f; public static Release1StagingHarnessResult TryDumpCashSlots(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); try { IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"dead drops could not be read, status {release1SmallCourtesyWorldReadStatus}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), list); } foreach (Release1SmallCourtesyDropCandidate item in (drops ?? Array.Empty()).Where((Release1SmallCourtesyDropCandidate candidate) => (object)candidate != null).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal)) { IReadOnlyList slots; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = world.TryReadDeadDropSlots(item.Guid, out slots); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"drop {item.Name} ({item.Guid}) slots {release1SmallCourtesyWorldReadStatus2}"); continue; } Release1SmallCourtesySlotSnapshot[] array = (from slot in slots ?? Array.Empty() where (object)slot != null && slot.Quantity > 0 orderby slot.SlotIndex select slot).ToArray(); if (array.Length == 0) { continue; } list.Add($"drop {item.Name} ({item.Guid}) has {array.Length} occupied slot(s)."); Release1SmallCourtesySlotSnapshot[] array2 = array; foreach (Release1SmallCourtesySlotSnapshot release1SmallCourtesySlotSnapshot in array2) { float balance; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus3 = world.TryReadDeadDropSlotCashBalance(item.Guid, release1SmallCourtesySlotSnapshot.SlotIndex, out balance); string value = ((release1SmallCourtesyWorldReadStatus3 == Release1SmallCourtesyWorldReadStatus.Ready) ? $" value {balance}" : string.Empty); list.Add($"slot {release1SmallCourtesySlotSnapshot.SlotIndex} product {release1SmallCourtesySlotSnapshot.ProductId} quantity {release1SmallCourtesySlotSnapshot.Quantity} legacy monetaryValue {release1SmallCourtesySlotSnapshot.MonetaryValue} cash balance read {release1SmallCourtesyWorldReadStatus3}{value}"); } return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } list.Add("no dead drop held an occupied slot."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } catch (Exception ex) { list.Add("an exception was thrown while dumping cash slots: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } public static Release1StagingHarnessResult TryDecrementFirstCashSlot(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); string text = null; int num = -1; try { IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"dead drops could not be read, status {release1SmallCourtesyWorldReadStatus}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), list); } foreach (Release1SmallCourtesyDropCandidate item in (drops ?? Array.Empty()).Where((Release1SmallCourtesyDropCandidate candidate) => (object)candidate != null).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal)) { if (world.TryReadDeadDropSlots(item.Guid, out IReadOnlyList slots) != Release1SmallCourtesyWorldReadStatus.Ready) { continue; } foreach (Release1SmallCourtesySlotSnapshot item2 in from candidate in slots ?? Array.Empty() where (object)candidate != null && candidate.Quantity > 0 orderby candidate.SlotIndex select candidate) { if (world.TryReadDeadDropSlotCashBalance(item.Guid, item2.SlotIndex, out var balance) == Release1SmallCourtesyWorldReadStatus.Ready && !(balance < 100f)) { list.Add($"drop {item.Name} ({item.Guid}) slot {item2.SlotIndex} pre balance {balance}"); Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = world.TrySetSlotLocked(item.Guid, item2.SlotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { list.Add($"the slot could not be locked, status {release1SmallCourtesyWorldMutationStatus}."); return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus), list); } text = item.Guid; num = item2.SlotIndex; Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus2 = world.TryChangeDeadDropSlotCashBalance(item.Guid, item2.SlotIndex, -100f); list.Add($"decrement by {100f} result {release1SmallCourtesyWorldMutationStatus2}"); if (release1SmallCourtesyWorldMutationStatus2 != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus2), list); } float balance2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = world.TryReadDeadDropSlotCashBalance(item.Guid, item2.SlotIndex, out balance2); string value = ((release1SmallCourtesyWorldReadStatus2 == Release1SmallCourtesyWorldReadStatus.Ready) ? $" value {balance2}" : string.Empty); list.Add($"post balance read {release1SmallCourtesyWorldReadStatus2}{value}"); float num2 = balance - 100f; list.Add($"expected post balance {num2}, observed {((release1SmallCourtesyWorldReadStatus2 == Release1SmallCourtesyWorldReadStatus.Ready) ? balance2.ToString() : "n/a")}"); return new Release1StagingHarnessResult((release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready || balance2 != num2) ? Release1StagingHarnessStatus.Faulted : Release1StagingHarnessStatus.Succeeded, list); } } } list.Add($"no dead drop slot held a cash balance of at least {100f}."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } catch (Exception ex) { list.Add("an exception was thrown while decrementing: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } finally { if (text != null && num >= 0) { try { world.TrySetSlotLocked(text, num, locked: false); } catch (Exception) { } } } } private static Release1StagingHarnessStatus MapReadStatus(Release1SmallCourtesyWorldReadStatus status) { return status switch { Release1SmallCourtesyWorldReadStatus.NotAuthoritative => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldReadStatus.Pending => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } private static Release1StagingHarnessStatus MapMutationStatus(Release1SmallCourtesyWorldMutationStatus status) { return status switch { Release1SmallCourtesyWorldMutationStatus.Succeeded => Release1StagingHarnessStatus.Succeeded, Release1SmallCourtesyWorldMutationStatus.Rejected => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldMutationStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } } public static class Release1TheEnvelopeClosetCashGateHarness { public const float DecrementAmount = 100f; public static Release1StagingHarnessResult TryDumpClosetCashSlots(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); try { if (!TryReadReadyRoom(world, list, out Release1HoldRoomSnapshot room, out Release1StagingHarnessResult failure)) { return failure; } list.Add($"hold room readiness: {room.Readiness}, closets {room.Closets.Count}"); foreach (Release1HoldRoomClosetSnapshot item in room.Closets.OrderBy((Release1HoldRoomClosetSnapshot candidate) => candidate.ClosetGuid, StringComparer.Ordinal)) { Release1SmallCourtesySlotSnapshot[] array = (from slot in item.Slots where slot.Quantity > 0 orderby slot.SlotIndex select slot).ToArray(); if (array.Length != 0) { list.Add($"closet {item.ClosetGuid} has {array.Length} occupied slot(s) of {item.SlotCount}."); Release1SmallCourtesySlotSnapshot[] array2 = array; foreach (Release1SmallCourtesySlotSnapshot release1SmallCourtesySlotSnapshot in array2) { float balance; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadHoldRoomSlotCashBalance(item.ClosetGuid, release1SmallCourtesySlotSnapshot.SlotIndex, out balance); string value = ((release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready) ? $" value {balance}" : string.Empty); list.Add($"slot {release1SmallCourtesySlotSnapshot.SlotIndex} product {release1SmallCourtesySlotSnapshot.ProductId} quantity {release1SmallCourtesySlotSnapshot.Quantity} legacy monetaryValue {release1SmallCourtesySlotSnapshot.MonetaryValue} cash balance read {release1SmallCourtesyWorldReadStatus}{value}"); } return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } } list.Add("no closet held an occupied slot."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } catch (Exception ex) { list.Add("an exception was thrown while dumping closet cash slots: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } public static Release1StagingHarnessResult TryDecrementFirstClosetCashSlot(IRelease1SmallCourtesyWorld world) { if (world == null) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { "the world was not available." }); } List list = new List(); string text = null; int num = -1; try { if (!TryReadReadyRoom(world, list, out Release1HoldRoomSnapshot room, out Release1StagingHarnessResult failure)) { return failure; } foreach (Release1HoldRoomClosetSnapshot item in room.Closets.OrderBy((Release1HoldRoomClosetSnapshot candidate) => candidate.ClosetGuid, StringComparer.Ordinal)) { foreach (Release1SmallCourtesySlotSnapshot item2 in from candidate in item.Slots where candidate.Quantity > 0 orderby candidate.SlotIndex select candidate) { if (world.TryReadHoldRoomSlotCashBalance(item.ClosetGuid, item2.SlotIndex, out var balance) == Release1SmallCourtesyWorldReadStatus.Ready && !(balance < 100f)) { list.Add($"closet {item.ClosetGuid} slot {item2.SlotIndex} pre balance {balance}"); Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = world.TrySetHoldRoomSlotLocked(item.ClosetGuid, item2.SlotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { list.Add($"the slot could not be locked, status {release1SmallCourtesyWorldMutationStatus}."); return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus), list); } text = item.ClosetGuid; num = item2.SlotIndex; Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus2 = world.TryChangeHoldRoomSlotCashBalance(item.ClosetGuid, item2.SlotIndex, -100f); list.Add($"decrement by {100f} result {release1SmallCourtesyWorldMutationStatus2}"); if (release1SmallCourtesyWorldMutationStatus2 != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus2), list); } float balance2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadHoldRoomSlotCashBalance(item.ClosetGuid, item2.SlotIndex, out balance2); bool flag = release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready; float num2 = balance - 100f; list.Add($"post balance read {release1SmallCourtesyWorldReadStatus}{(flag ? $" value {balance2}" : string.Empty)}"); list.Add($"expected post balance {num2}, observed {(flag ? balance2.ToString() : "n/a")}"); return new Release1StagingHarnessResult((!flag || balance2 != num2) ? Release1StagingHarnessStatus.Faulted : Release1StagingHarnessStatus.Succeeded, list); } } } list.Add($"no closet slot held a cash balance of at least {100f}."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Unavailable, list); } catch (Exception ex) { list.Add("an exception was thrown while decrementing: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } finally { if (text != null && num >= 0) { try { world.TrySetHoldRoomSlotLocked(text, num, locked: false); } catch (Exception) { } } } } private static bool TryReadReadyRoom(IRelease1SmallCourtesyWorld world, List lines, out Release1HoldRoomSnapshot? room, out Release1StagingHarnessResult failure) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadHoldRoom(out room); if (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready && (object)room != null && room.Readiness == Release1HoldRoomReadiness.Ready) { failure = null; return true; } lines.Add($"the hold room could not be read, status {release1SmallCourtesyWorldReadStatus}, readiness {room?.Readiness}."); failure = new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), lines); return false; } private static Release1StagingHarnessStatus MapReadStatus(Release1SmallCourtesyWorldReadStatus status) { return status switch { Release1SmallCourtesyWorldReadStatus.NotAuthoritative => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldReadStatus.Pending => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Ready => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } private static Release1StagingHarnessStatus MapMutationStatus(Release1SmallCourtesyWorldMutationStatus status) { return status switch { Release1SmallCourtesyWorldMutationStatus.Succeeded => Release1StagingHarnessStatus.Succeeded, Release1SmallCourtesyWorldMutationStatus.Rejected => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldMutationStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } } public sealed class Release1TheEnvelopeComposition : IDisposable { private bool _disposed; public Release1TheEnvelopeMissionService Service { get; } public Release1TheEnvelopePresenter Presenter { get; } public Release1TheEnvelopeComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService phone, Action? log = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(world, "world"); ArgumentNullException.ThrowIfNull(phone, "phone"); Service = new Release1TheEnvelopeMissionService(story, world, phone, log); Presenter = new Release1TheEnvelopePresenter(Service, story, log); } public void OnLoadComplete() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnLoadComplete(); } } public void OnPreLoad() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnPreLoad(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); } } public void Update() { if (!_disposed) { Service.Update(); } } public void Dispose() { if (!_disposed) { _disposed = true; Presenter.Dispose(); Service.Dispose(); } } } public enum Release1TheEnvelopeOfferStatus { Inactive, Available, Ineligible, Pending, Saving, Unavailable, Dismissed, Disposed } public enum Release1TheEnvelopeReviewStatus { Ready, Inactive, Ineligible, Pending, Saving, Unavailable, Dismissed, Disposed } public enum Release1TheEnvelopeDecisionStatus { Accepted, Deferred, Dismissed, ReviewRequired, QuoteChanged, PersistenceDeferred, Rejected, Inactive, Disposed } public enum Release1TheEnvelopeDepositStatus { NoWork, Unavailable, Held, Ambiguous, Rejected, AwaitingAppliedSave, Committed } public sealed record Release1TheEnvelopeQuote(Release1TheEnvelopeAssignment Assignment, double? DeadlineDurationHours) { public void Validate() { Assignment.Validate(); if (Assignment.Mode == Release1TheEnvelopeAssignmentMode.Recovery) { if (DeadlineDurationHours.HasValue) { throw new ArgumentException("Recovery quotes cannot have a deadline.", "DeadlineDurationHours"); } } else if (DeadlineDurationHours.GetValueOrDefault() != 24.0) { throw new ArgumentException("Timed Envelope stages require a 24-hour duration.", "DeadlineDurationHours"); } } } public sealed record Release1TheEnvelopeReviewResult(Release1TheEnvelopeReviewStatus Status, Release1TheEnvelopeQuote? Quote, string Message); public sealed record Release1TheEnvelopeDecisionResult(Release1TheEnvelopeDecisionStatus Status, string Message); public sealed class Release1TheEnvelopeMissionService : IDisposable { private const string TermsVersion = "the-envelope-v1"; private const double TimedStageDurationHours = 24.0; private const string EffectScope = "oc-the-envelope"; private const double CashReadTolerance = 0.01; private const string ArthurCallerLabel = "Arthur Selby"; private static readonly string[] ArthurWarningStages = new string[3] { "The envelope did not land and the window is closed. You really want me to be your enemy?", "Nell has arranged one more chance. A smaller number, twenty four hours from acceptance.", "Do not make her ask twice." }; private static readonly string[] ArthurEndingStages = new string[1] { "You might have stumbled your way through this, but now we see if you can really handle the fucking big times." }; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1PhoneCallService? _phone; private readonly Action _log; private bool _loadActive; private bool _saving; private bool _dismissed; private bool _disposed; private Release1TheEnvelopeQuote? _reviewedQuote; private double? _lastConvergenceGameMinute; private long _lastConvergenceRevision = -1L; private readonly HashSet _reportedHolds = new HashSet(StringComparer.Ordinal); private string? _lockedClosetGuid; private readonly List _lockedSlotIndexes = new List(); public Release1TheEnvelopeOfferStatus OfferStatus { get; private set; } public Release1TheEnvelopeQuote? ReviewedQuote => _reviewedQuote; public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public Release1TheEnvelopeMissionService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService? phone = null, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _phone = phone; _log = log ?? ((Action)delegate { }); } public void OnLoadComplete() { if (!_disposed && !_loadActive) { _loadActive = true; _saving = false; _dismissed = false; _reviewedQuote = null; _lastConvergenceGameMinute = null; _reportedHolds.Clear(); RefreshOfferStatus(); Converge(); } } public void OnPreLoad() { if (!_disposed) { ReleaseSlotLocks(); _loadActive = false; _saving = false; _dismissed = false; _reviewedQuote = null; _lastConvergenceGameMinute = null; _reportedHolds.Clear(); OfferStatus = Release1TheEnvelopeOfferStatus.Inactive; } } public void OnSaveStart() { if (!_disposed && _loadActive) { _saving = true; _lastConvergenceGameMinute = null; OfferStatus = Release1TheEnvelopeOfferStatus.Saving; } } public void OnSaveComplete() { if (!_disposed && _loadActive) { _saving = false; _lastConvergenceGameMinute = null; RefreshOfferStatus(); Converge(); } } public void Update() { if (_disposed || !_loadActive || _saving || !TryBeginConvergencePass()) { return; } Release1StoryHostContextSnapshot context; Release1TheEnvelopeReviewStatus release1TheEnvelopeReviewStatus = ReadMatchingContext(out context); if (release1TheEnvelopeReviewStatus != Release1TheEnvelopeReviewStatus.Ready) { OfferStatus = Map(release1TheEnvelopeReviewStatus); return; } Release1StoryState state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Accepted) { Release1MissionRecord release1MissionRecord = Mission(state); Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive) ? true : false); if (flag && release1MissionRecord.DeadlineGameTimeHours.HasValue && TryReadGameHours(out var gameHours) && gameHours >= release1MissionRecord.DeadlineGameTimeHours.Value && !TryReconcileExecutedConsumptionEffect(release1MissionRecord)) { Release1TransitionKind transition = ((release1MissionRecord.State == Release1MissionState.Active) ? Release1TransitionKind.RequiredFailure : Release1TransitionKind.MakeGoodFailed); string receipt = $"the-envelope-deadline-{DeadlineToken(release1MissionRecord.State)}-v1-a{release1MissionRecord.Attempt}"; _story.TryExecuteDurably(CreateCommand(transition, release1MissionRecord.Attempt, receipt, null, null, null)); RefreshOfferStatus(); } Converge(); } } public Release1TheEnvelopeReviewResult TryReview() { if (_disposed) { return Review(Release1TheEnvelopeReviewStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Review(Release1TheEnvelopeReviewStatus.Inactive, "No load epoch is active."); } if (_saving) { return Review(Release1TheEnvelopeReviewStatus.Saving, "Mission review is deferred during saving."); } if (_dismissed) { return Review(Release1TheEnvelopeReviewStatus.Dismissed, "Offer was dismissed for this load."); } Release1TheEnvelopeQuote quote; Release1TheEnvelopeReviewStatus release1TheEnvelopeReviewStatus = TryBuildQuote(out quote); if (release1TheEnvelopeReviewStatus != Release1TheEnvelopeReviewStatus.Ready) { _reviewedQuote = null; OfferStatus = Map(release1TheEnvelopeReviewStatus); return Review(release1TheEnvelopeReviewStatus, Message(release1TheEnvelopeReviewStatus)); } _reviewedQuote = quote; OfferStatus = Release1TheEnvelopeOfferStatus.Available; return new Release1TheEnvelopeReviewResult(Release1TheEnvelopeReviewStatus.Ready, quote, "The Envelope terms are ready for review."); } public Release1TheEnvelopeDecisionResult TryAccept() { if (_disposed) { return Decision(Release1TheEnvelopeDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _dismissed) { return Decision(Release1TheEnvelopeDecisionStatus.Inactive, "No active offer is available."); } if (_saving) { return Decision(Release1TheEnvelopeDecisionStatus.PersistenceDeferred, "Acceptance is deferred during saving."); } if ((object)_reviewedQuote == null) { return Decision(Release1TheEnvelopeDecisionStatus.ReviewRequired, "Review the current terms before accepting."); } if (TryBuildQuote(out Release1TheEnvelopeQuote quote) != Release1TheEnvelopeReviewStatus.Ready || quote != _reviewedQuote) { _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1TheEnvelopeDecisionStatus.QuoteChanged, "The terms changed; review again."); } if (!TryReadGameHours(out var gameHours)) { return Decision(Release1TheEnvelopeDecisionStatus.Rejected, "Canonical game time was unavailable."); } Release1TheEnvelopeAssignment assignment = quote.Assignment; Release1StoryCommand command = CreateCommand(assignment.Mode switch { Release1TheEnvelopeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1TheEnvelopeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1TheEnvelopeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }, assignment.Attempt, AcceptanceReceipt(assignment.Mode, assignment.Attempt), (assignment.Mode == Release1TheEnvelopeAssignmentMode.Primary) ? "the-envelope-v1" : null, gameHours, (assignment.Mode == Release1TheEnvelopeAssignmentMode.Recovery) ? ((double?)null) : new double?(gameHours + 24.0)); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteTheEnvelopeAcceptanceDurably(command, assignment); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1TheEnvelopeDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1TheEnvelopeDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } _reviewedQuote = null; _lastConvergenceGameMinute = null; RefreshOfferStatus(); Converge(); return Decision(Release1TheEnvelopeDecisionStatus.Accepted, "The Envelope stage was accepted."); } public Release1TheEnvelopeDecisionResult TryDefer() { if (_disposed) { return Decision(Release1TheEnvelopeDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _saving || ReadMatchingContext(out var _) != Release1TheEnvelopeReviewStatus.Ready) { return Decision(Release1TheEnvelopeDecisionStatus.Inactive, "No active offer is available."); } Release1StoryState state = _story.State; Release1MissionRecord release1MissionRecord = (((object)state == null) ? null : Mission(state)); if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted || (object)release1MissionRecord == null || release1MissionRecord.State != Release1MissionState.Offered) { return TryDismiss(); } string receipt = $"the-envelope-defer-v1-a{release1MissionRecord.Attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(Release1TransitionKind.MissionDeferred, release1MissionRecord.Attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1TheEnvelopeDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1TheEnvelopeDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } DismissForLoad(); return Decision(Release1TheEnvelopeDecisionStatus.Deferred, "The Envelope was deferred until a later load."); } public Release1TheEnvelopeDecisionResult TryDismiss() { if (_disposed) { return Decision(Release1TheEnvelopeDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Decision(Release1TheEnvelopeDecisionStatus.Inactive, "No load epoch is active."); } DismissForLoad(); return Decision(Release1TheEnvelopeDecisionStatus.Dismissed, "The Envelope was dismissed for this load."); } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; OfferStatus = Release1TheEnvelopeOfferStatus.Disposed; } } private Release1TheEnvelopeReviewStatus TryBuildQuote(out Release1TheEnvelopeQuote? quote) { try { return TryBuildQuoteCore(out quote); } catch { quote = null; return Release1TheEnvelopeReviewStatus.Unavailable; } } private Release1TheEnvelopeReviewStatus TryBuildQuoteCore(out Release1TheEnvelopeQuote? quote) { quote = null; Release1StoryHostContextSnapshot context; Release1TheEnvelopeReviewStatus release1TheEnvelopeReviewStatus = ReadMatchingContext(out context); if (release1TheEnvelopeReviewStatus != Release1TheEnvelopeReviewStatus.Ready) { return release1TheEnvelopeReviewStatus; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1TheEnvelopeReviewStatus.Ineligible; } if (state.Missions[Release1MissionCatalog.IndexOf("release1.keep-the-lights-off")].State != Release1MissionState.Satisfied) { return Release1TheEnvelopeReviewStatus.Ineligible; } Release1MissionRecord release1MissionRecord = Mission(state); Release1TheEnvelopeAssignmentMode? release1TheEnvelopeAssignmentMode = release1MissionRecord.State switch { Release1MissionState.Offered => Release1TheEnvelopeAssignmentMode.Primary, Release1MissionState.MakeGoodOffered => Release1TheEnvelopeAssignmentMode.MakeGood, Release1MissionState.RecoveryAvailable => Release1TheEnvelopeAssignmentMode.Recovery, _ => null, }; if (!release1TheEnvelopeAssignmentMode.HasValue) { return Release1TheEnvelopeReviewStatus.Ineligible; } int attempt = ((release1TheEnvelopeAssignmentMode == Release1TheEnvelopeAssignmentMode.Primary) ? release1MissionRecord.Attempt : (release1MissionRecord.Attempt + 1)); Release1TransitionKind transitionKind = release1TheEnvelopeAssignmentMode switch { Release1TheEnvelopeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1TheEnvelopeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1TheEnvelopeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string value = Release1LogicalCorrelation.Create(context.PlayerId, "release1.the-envelope", attempt, transitionKind, AcceptanceReceipt(release1TheEnvelopeAssignmentMode.Value, attempt)).Value; if (!Release1TheEnvelopeAssignmentSelector.TrySelect(release1TheEnvelopeAssignmentMode.Value, attempt, value, out Release1TheEnvelopeAssignment assignment, out Release1TheEnvelopeSelectionStatus _)) { return Release1TheEnvelopeReviewStatus.Unavailable; } quote = new Release1TheEnvelopeQuote(assignment, (release1TheEnvelopeAssignmentMode == Release1TheEnvelopeAssignmentMode.Recovery) ? ((double?)null) : new double?(24.0)); quote.Validate(); return Release1TheEnvelopeReviewStatus.Ready; } private void RefreshOfferStatus() { if (_disposed) { OfferStatus = Release1TheEnvelopeOfferStatus.Disposed; } else if (!_loadActive) { OfferStatus = Release1TheEnvelopeOfferStatus.Inactive; } else if (_saving) { OfferStatus = Release1TheEnvelopeOfferStatus.Saving; } else if (_dismissed) { OfferStatus = Release1TheEnvelopeOfferStatus.Dismissed; } else { OfferStatus = Map(TryBuildQuote(out Release1TheEnvelopeQuote _)); } } private Release1TheEnvelopeReviewStatus ReadMatchingContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1TheEnvelopeReviewStatus.Inactive; } try { Release1StoryHostContextSnapshot context2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadContext(out context2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!SameContext(snapshot, context2)) { return Release1TheEnvelopeReviewStatus.Inactive; } context = snapshot; return Release1TheEnvelopeReviewStatus.Ready; } catch { return Release1TheEnvelopeReviewStatus.Unavailable; } } private bool TryReadGameHours(out double gameHours) { gameHours = 0.0; try { if (_world.TryReadCanonicalTotalMinutes(out var totalMinutes) != Release1SmallCourtesyWorldReadStatus.Ready || !double.IsFinite(totalMinutes) || totalMinutes < 0.0) { return false; } gameHours = totalMinutes / 60.0; return double.IsFinite(gameHours); } catch { return false; } } private Release1StoryCommand CreateCommand(Release1TransitionKind transition, int attempt, string receipt, string? termsVersion, double? acceptedHours, double? deadlineHours) { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { throw new InvalidOperationException("Story context was not active."); } return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, "release1.the-envelope", attempt, transition, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.the-envelope", attempt, transition, receipt).Value, termsVersion, acceptedHours, deadlineHours); } private void DismissForLoad() { _dismissed = true; _reviewedQuote = null; OfferStatus = Release1TheEnvelopeOfferStatus.Dismissed; } internal static Release1MissionRecord Mission(Release1StoryState state) { return state.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; } private static string AcceptanceReceipt(Release1TheEnvelopeAssignmentMode mode, int attempt) { return $"the-envelope-{mode.ToString().ToLowerInvariant()}-accept-v1-a{attempt}"; } private static string DeadlineToken(Release1MissionState state) { if (state != Release1MissionState.Active) { return "make-good"; } return "primary"; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private static Release1TheEnvelopeReviewResult Review(Release1TheEnvelopeReviewStatus status, string message) { return new Release1TheEnvelopeReviewResult(status, null, message); } private static Release1TheEnvelopeDecisionResult Decision(Release1TheEnvelopeDecisionStatus status, string message) { return new Release1TheEnvelopeDecisionResult(status, message); } private static Release1TheEnvelopeReviewStatus Map(Release1SmallCourtesyWorldReadStatus status) { if ((uint)(status - 1) <= 1u) { return Release1TheEnvelopeReviewStatus.Pending; } return Release1TheEnvelopeReviewStatus.Unavailable; } private static Release1TheEnvelopeOfferStatus Map(Release1TheEnvelopeReviewStatus status) { return status switch { Release1TheEnvelopeReviewStatus.Ready => Release1TheEnvelopeOfferStatus.Available, Release1TheEnvelopeReviewStatus.Inactive => Release1TheEnvelopeOfferStatus.Inactive, Release1TheEnvelopeReviewStatus.Ineligible => Release1TheEnvelopeOfferStatus.Ineligible, Release1TheEnvelopeReviewStatus.Pending => Release1TheEnvelopeOfferStatus.Pending, Release1TheEnvelopeReviewStatus.Saving => Release1TheEnvelopeOfferStatus.Saving, Release1TheEnvelopeReviewStatus.Dismissed => Release1TheEnvelopeOfferStatus.Dismissed, Release1TheEnvelopeReviewStatus.Disposed => Release1TheEnvelopeOfferStatus.Disposed, _ => Release1TheEnvelopeOfferStatus.Unavailable, }; } private static string Message(Release1TheEnvelopeReviewStatus status) { return status switch { Release1TheEnvelopeReviewStatus.Pending => "World eligibility is still pending.", Release1TheEnvelopeReviewStatus.Ineligible => "The Envelope is not currently offerable.", Release1TheEnvelopeReviewStatus.Inactive => "The canonical story context is inactive.", _ => "The Envelope world data is unavailable.", }; } public Release1TheEnvelopeDepositStatus ReconcileDeposit() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1TheEnvelopeReviewStatus.Ready) { return Release1TheEnvelopeDepositStatus.NoWork; } if (_lockedClosetGuid != null) { ReleaseSlotLocks(); } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1TheEnvelopeDepositStatus.NoWork; } Release1MissionRecord mission = Mission(state); Release1TheEnvelopeAssignment release1TheEnvelopeAssignment = state.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == mission.Attempt); if ((object)release1TheEnvelopeAssignment == null) { return Release1TheEnvelopeDepositStatus.NoWork; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == ConsumptionEffectId(mission.Attempt)); if ((object)release1NativeEffectJournalEntry == null) { if (!TryGetActiveStage(out Release1MissionRecord _, out Release1TheEnvelopeAssignment _, out string authorization)) { return Release1TheEnvelopeDepositStatus.NoWork; } return BeginDeposit(mission, release1TheEnvelopeAssignment, authorization); } return ContinueDeposit(mission, release1TheEnvelopeAssignment, release1NativeEffectJournalEntry); } private bool TryReadRoomCash(Release1TheEnvelopeAssignment assignment, out Release1HoldRoomSnapshot? room, out IReadOnlyList closets) { room = null; closets = Array.Empty(); try { if (_world.TryReadHoldRoom(out Release1HoldRoomSnapshot room2) != Release1SmallCourtesyWorldReadStatus.Ready || (object)room2 == null) { return false; } room = room2; if (room2.Readiness != Release1HoldRoomReadiness.Ready || room2.Closets.Count != assignment.ExpectedClosetCount) { return true; } List list = new List(room2.Closets.Count); foreach (Release1HoldRoomClosetSnapshot item in room2.Closets.OrderBy((Release1HoldRoomClosetSnapshot candidate) => candidate.ClosetGuid, StringComparer.Ordinal)) { List list2 = new List(); bool readable = true; foreach (Release1SmallCourtesySlotSnapshot item2 in from candidate in item.Slots where candidate.Quantity > 0 orderby candidate.SlotIndex select candidate) { float balance; switch (_world.TryReadHoldRoomSlotCashBalance(item.ClosetGuid, item2.SlotIndex, out balance)) { case Release1SmallCourtesyWorldReadStatus.Ready: list2.Add(new Release1TheEnvelopeCashSlot(item2.SlotIndex, balance)); break; default: readable = false; break; case Release1SmallCourtesyWorldReadStatus.Unavailable: break; } } list.Add(new Release1TheEnvelopeClosetCash(item.ClosetGuid, readable, list2)); } closets = list; return true; } catch { room = null; closets = Array.Empty(); return false; } } private Release1TheEnvelopeDepositStatus BeginDeposit(Release1MissionRecord mission, Release1TheEnvelopeAssignment assignment, string authorization) { if (!TryReadRoomCash(assignment, out Release1HoldRoomSnapshot room, out IReadOnlyList closets)) { ReportHoldOnce(assignment.Attempt, "the hold room could not be read this pass; nothing was changed."); return Release1TheEnvelopeDepositStatus.Unavailable; } string cashClosetGuid; double cashSum; switch (Release1TheEnvelopeClosetClassifier.Classify(room, closets, assignment.ExpectedClosetCount, assignment.AmountWholeDollars, out cashClosetGuid, out cashSum)) { case Release1TheEnvelopeClosetObservation.RoomNotReady: ReportHoldOnce(assignment.Attempt, "the hold room did not read ready with nine closets; the pass changed nothing."); return Release1TheEnvelopeDepositStatus.Unavailable; case Release1TheEnvelopeClosetObservation.ClosetUnreadable: ReportHoldOnce(assignment.Attempt, "a closet read back empty or unreadable; a zero length read is not treated as empty."); return Release1TheEnvelopeDepositStatus.Unavailable; case Release1TheEnvelopeClosetObservation.NoCash: return Release1TheEnvelopeDepositStatus.NoWork; case Release1TheEnvelopeClosetObservation.Spread: SetProgress(assignment.Attempt, null, null, spreadNoticed: true); ReportHoldOnce(assignment.Attempt, "cash is in more than one closet; the deposit will consume none of it."); return Release1TheEnvelopeDepositStatus.Held; case Release1TheEnvelopeClosetObservation.Shortfall: SetProgress(assignment.Attempt, cashSum, assignment.AmountWholeDollars - cashSum); return Release1TheEnvelopeDepositStatus.NoWork; default: { if (!Release1TheEnvelopeClosetPlan.TryBuild(closets.Single((Release1TheEnvelopeClosetCash closet) => string.Equals(closet.ClosetGuid, cashClosetGuid, StringComparison.Ordinal)).CashSlots, cashClosetGuid, assignment.AmountWholeDollars, out Release1TheEnvelopeClosetPlan plan) || (object)plan == null) { SetProgress(assignment.Attempt, cashSum); ReportHoldOnce(assignment.Attempt, "the closet's cash could not be planned within the effect identity bound; nothing was consumed."); return Release1TheEnvelopeDepositStatus.Held; } return BeginTransaction(mission, assignment, authorization, plan); } } } private Release1TheEnvelopeDepositStatus BeginTransaction(Release1MissionRecord mission, Release1TheEnvelopeAssignment assignment, string authorization, Release1TheEnvelopeClosetPlan plan) { Release1NativeEffectJournalEntry prepared = new Release1NativeEffectJournalEntry(ConsumptionEffectId(assignment.Attempt), "release1.the-envelope", assignment.Attempt, "CashTransfer", plan.ClosetGuid, "oc-the-envelope", plan.Serialize(), Release1NativeEffectPhase.Prepared, null, _story.State.Revision + 1, null, authorization, ExecutionBlocked: false, mission.Revision); if (!_story.TryPrepareNativeEffect(prepared).Accepted) { return Release1TheEnvelopeDepositStatus.Rejected; } Release1NativeEffectJournalEntry effect = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == prepared.EffectId); return ConsumeEnvelope(plan, effect) ?? FinishDeposit(assignment, effect, authorization); } private Release1TheEnvelopeDepositStatus? ConsumeEnvelope(Release1TheEnvelopeClosetPlan plan, Release1NativeEffectJournalEntry effect) { if (!TryAcquireSlotLocks(plan.ClosetGuid, plan.Slots)) { ReleaseSlotLocks(); ReportHoldOnce(effect.Attempt, "a closet slot lock could not be taken this pass; the deposit will retry once the lock is free, and no cash has moved."); return Release1TheEnvelopeDepositStatus.Held; } try { foreach (Release1TheEnvelopePlannedSlot slot in plan.Slots) { if (!MatchesSlotBalance(plan.ClosetGuid, slot.SlotIndex, slot.PostBalance)) { if (!MatchesSlotBalance(plan.ClosetGuid, slot.SlotIndex, slot.PreBalance)) { BlockConsumption(effect); return Release1TheEnvelopeDepositStatus.Ambiguous; } float num = (float)(slot.PreBalance - slot.PostBalance); Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeHoldRoomSlotCashBalance(plan.ClosetGuid, slot.SlotIndex, 0f - num); } catch { BlockConsumption(effect); return Release1TheEnvelopeDepositStatus.Ambiguous; } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { BlockConsumption(effect); return Release1TheEnvelopeDepositStatus.Ambiguous; } if (!MatchesSlotBalance(plan.ClosetGuid, slot.SlotIndex, slot.PostBalance)) { BlockConsumption(effect); return Release1TheEnvelopeDepositStatus.Ambiguous; } } } return MarkConsumptionApplied(effect, plan.Slots.Sum((Release1TheEnvelopePlannedSlot slot) => slot.PostBalance)) ? ((Release1TheEnvelopeDepositStatus?)null) : new Release1TheEnvelopeDepositStatus?(Release1TheEnvelopeDepositStatus.Ambiguous); } finally { ReleaseSlotLocks(); } } private bool MatchesSlotBalance(string closetGuid, int slotIndex, double expected) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus; float balance; try { release1SmallCourtesyWorldReadStatus = _world.TryReadHoldRoomSlotCashBalance(closetGuid, slotIndex, out balance); } catch { return false; } if (expected != 0.0) { if (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready) { return Math.Abs((double)balance - expected) <= 0.01; } return false; } return release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Unavailable; } private bool TryAcquireSlotLocks(string closetGuid, IReadOnlyList slots) { if (_lockedClosetGuid != null && !string.Equals(_lockedClosetGuid, closetGuid, StringComparison.Ordinal)) { return false; } _lockedClosetGuid = closetGuid; foreach (Release1TheEnvelopePlannedSlot slot in slots) { if (_lockedSlotIndexes.Contains(slot.SlotIndex)) { continue; } try { if (_world.TrySetHoldRoomSlotLocked(closetGuid, slot.SlotIndex, locked: true) != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return false; } } catch { return false; } _lockedSlotIndexes.Add(slot.SlotIndex); } return true; } private bool MarkConsumptionApplied(Release1NativeEffectJournalEntry effect, double postSum) { if (!_story.TryMarkNativeEffectApplied(effect.EffectId, $"the-envelope-cash-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted) { return false; } SetProgress(effect.Attempt, postSum); return true; } private Release1TheEnvelopeDepositStatus BlockConsumption(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } ReleaseSlotLocks(); return Release1TheEnvelopeDepositStatus.Ambiguous; } private Release1TheEnvelopeDepositStatus ContinueDeposit(Release1MissionRecord mission, Release1TheEnvelopeAssignment assignment, Release1NativeEffectJournalEntry effect) { string authorizedStoryCorrelationId = effect.AuthorizedStoryCorrelationId; string closetGuid = effect.SourceIdentity ?? string.Empty; if (string.IsNullOrEmpty(authorizedStoryCorrelationId) || !Release1TheEnvelopeClosetPlan.TryParse(effect.AmountOrCargoIdentity, closetGuid, out Release1TheEnvelopeClosetPlan plan) || (object)plan == null || effect.Attempt != mission.Attempt || !string.Equals(effect.EffectKind, "CashTransfer", StringComparison.Ordinal) || !string.Equals(effect.DestinationIdentity, "oc-the-envelope", StringComparison.Ordinal) || plan.ConsumedAmount != assignment.AmountWholeDollars) { return BlockConsumption(effect); } if (effect.ExecutionBlocked) { return Release1TheEnvelopeDepositStatus.Ambiguous; } if (effect.Phase == Release1NativeEffectPhase.Prepared) { if (!TryReadRoomCash(assignment, out Release1HoldRoomSnapshot room, out IReadOnlyList _) || (object)room == null || room.Readiness != Release1HoldRoomReadiness.Ready || room.Closets.Count != assignment.ExpectedClosetCount || room.Closets.All((Release1HoldRoomClosetSnapshot closet) => !string.Equals(closet.ClosetGuid, closetGuid, StringComparison.Ordinal)) || room.Closets.Any((Release1HoldRoomClosetSnapshot closet) => closet.SlotCount == 0)) { ReportHoldOnce(assignment.Attempt, "the hold room could not be read back to reconcile the deposit; a zero length or uninitialized read is not treated as confirmation."); return Release1TheEnvelopeDepositStatus.Unavailable; } if (plan.Slots.All((Release1TheEnvelopePlannedSlot slot) => MatchesSlotBalance(closetGuid, slot.SlotIndex, slot.PostBalance))) { if (!MarkConsumptionApplied(effect, plan.Slots.Sum((Release1TheEnvelopePlannedSlot slot) => slot.PostBalance))) { return Release1TheEnvelopeDepositStatus.Rejected; } } else { if (!plan.Slots.All((Release1TheEnvelopePlannedSlot slot) => MatchesSlotBalance(closetGuid, slot.SlotIndex, slot.PreBalance) || MatchesSlotBalance(closetGuid, slot.SlotIndex, slot.PostBalance))) { return BlockConsumption(effect); } Release1MissionState state = mission.State; if ((state != Release1MissionState.Active && state != Release1MissionState.MakeGoodActive && state != Release1MissionState.RecoveryActive) || 1 == 0) { ReportHoldOnce(assignment.Attempt, "the stage is no longer active for this attempt; the deposit will not consume the closet's cash."); return Release1TheEnvelopeDepositStatus.Held; } Release1TheEnvelopeDepositStatus? release1TheEnvelopeDepositStatus = ConsumeEnvelope(plan, effect); if (release1TheEnvelopeDepositStatus.HasValue) { return release1TheEnvelopeDepositStatus.GetValueOrDefault(); } } } return FinishDeposit(assignment, effect, authorizedStoryCorrelationId); } private Release1TheEnvelopeDepositStatus FinishDeposit(Release1TheEnvelopeAssignment assignment, Release1NativeEffectJournalEntry effect, string authorization) { long revision = _story.State.Revision; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effect.EffectId); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { return Release1TheEnvelopeDepositStatus.Ambiguous; } if (!TryComplete(release1NativeEffectJournalEntry, assignment)) { return Release1TheEnvelopeDepositStatus.Rejected; } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied && revision <= _story.LastPersistedRevision) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorization, revision); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(assignment.Attempt, "the consumption commit after a save was rejected: " + release1StoryRuntimeCommandResult.Message); return Release1TheEnvelopeDepositStatus.Rejected; } return Release1TheEnvelopeDepositStatus.Committed; } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Committed) { return Release1TheEnvelopeDepositStatus.NoWork; } return Release1TheEnvelopeDepositStatus.AwaitingAppliedSave; } private bool TryComplete(Release1NativeEffectJournalEntry consumption, Release1TheEnvelopeAssignment assignment) { Release1StoryState state = _story.State; if ((object)state == null) { return false; } Release1MissionRecord release1MissionRecord = Mission(state); if (release1MissionRecord.State == Release1MissionState.Satisfied) { return true; } Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive || state2 == Release1MissionState.RecoveryActive) ? true : false); if (!flag || consumption.Phase != Release1NativeEffectPhase.Applied || consumption.ExecutionBlocked || consumption.Attempt != release1MissionRecord.Attempt) { return false; } if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return false; } string receiptId = $"the-envelope-complete-v1-a{release1MissionRecord.Attempt}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.the-envelope", release1MissionRecord.Attempt, Release1TransitionKind.MissionCompleted, receiptId).Value; string text = $"the-envelope-recognition-v1-a{release1MissionRecord.Attempt}"; Guid sessionEpoch = snapshot.SessionEpoch; long loadEpoch = snapshot.LoadEpoch; string playerId = snapshot.PlayerId; int attempt = release1MissionRecord.Attempt; Release1CompletionTiming? completionTiming = ((release1MissionRecord.State != Release1MissionState.Active) ? Release1CompletionTiming.Late : Release1CompletionTiming.OnTime); string rewardAuthorizationReceiptId = $"the-envelope-reward-auth-v1-a{release1MissionRecord.Attempt}"; string recognitionReceiptId = text; Release1StoryCommand command = new Release1StoryCommand(sessionEpoch, loadEpoch, playerId, "release1.the-envelope", attempt, Release1TransitionKind.MissionCompleted, receiptId, value, null, null, null, completionTiming, rewardAuthorizationReceiptId, null, recognitionReceiptId); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecute(command); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(release1MissionRecord.Attempt, "completing the mission after the deposit was consumed was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } private void TryQueueArthur() { if (_phone == null || _disposed || !_loadActive || _saving) { return; } Release1StoryState state = _story.State; if ((object)state != null && _story.TryGetActiveContext(out var snapshot, out var _)) { Release1MissionRecord release1MissionRecord = Mission(state); if (release1MissionRecord.State == Release1MissionState.Satisfied) { TryQueueArthurOnce(snapshot, release1MissionRecord, "te-recognition-v1", ArthurEndingStages); } else if (release1MissionRecord.State == Release1MissionState.MakeGoodOffered && release1MissionRecord.LastOutcome == Release1MissionOutcome.RequiredFailure) { TryQueueArthurOnce(snapshot, release1MissionRecord, "te-warning-v1", ArthurWarningStages); } } } private void TryQueueArthurOnce(Release1StoryHostContextSnapshot context, Release1MissionRecord mission, string receipt, IReadOnlyList stages) { try { Release1PhoneCallRequest request = Release1PhoneCallRequest.Create(context, "release1.the-envelope", mission.Attempt, Release1PhoneCallCorrelation.Create(context.PlayerId, "release1.the-envelope", mission.Attempt, Release1PhoneCallRole.Arthur, receipt), Release1PhoneCallRole.Arthur, "Arthur Selby", stages, Release1LogicalCorrelation.Create(context.PlayerId, "release1.the-envelope", mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-te-accepted-v1").Value); _phone.TryQueue(request); } catch (Exception ex) { _log("The Envelope Arthur request was not published: " + ex.GetType().Name); } } private void SetProgress(int attempt, double? observedBalance = null, double? lastShortfallNoticed = null, bool spreadNoticed = false) { Release1TheEnvelopeProgress release1TheEnvelopeProgress = _story.State?.TheEnvelopeProgress.SingleOrDefault((Release1TheEnvelopeProgress progress) => progress.Attempt == attempt) ?? Release1TheEnvelopeProgress.Fresh(attempt); Release1TheEnvelopeProgress release1TheEnvelopeProgress2 = release1TheEnvelopeProgress with { ObservedBalance = (observedBalance ?? release1TheEnvelopeProgress.ObservedBalance), LastShortfallNoticed = lastShortfallNoticed, SpreadNoticed = (release1TheEnvelopeProgress.SpreadNoticed || spreadNoticed) }; if (release1TheEnvelopeProgress2 != release1TheEnvelopeProgress) { _story.TrySetTheEnvelopeProgress(release1TheEnvelopeProgress2); } } private static string ConsumptionEffectId(int attempt) { return $"the-envelope-cash-v1-a{attempt}"; } internal static Release1TheEnvelopeAssignmentMode? ExpectedAssignmentMode(Release1MissionState missionState) { switch (missionState) { case Release1MissionState.Accepted: case Release1MissionState.Active: return Release1TheEnvelopeAssignmentMode.Primary; case Release1MissionState.MakeGoodOffered: case Release1MissionState.MakeGoodActive: return Release1TheEnvelopeAssignmentMode.MakeGood; case Release1MissionState.RecoveryAvailable: case Release1MissionState.RecoveryActive: return Release1TheEnvelopeAssignmentMode.Recovery; default: return null; } } private bool TryGetActiveStage(out Release1MissionRecord mission, out Release1TheEnvelopeAssignment assignment, out string authorization) { mission = null; assignment = null; authorization = string.Empty; Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return false; } mission = Mission(state); Release1TheEnvelopeAssignmentMode? expectedMode = ExpectedAssignmentMode(mission.State); if (!expectedMode.HasValue) { return false; } int missionAttempt = mission.Attempt; assignment = state.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == missionAttempt && candidate.Mode == expectedMode.Value); if ((object)assignment == null) { return false; } authorization = ((expectedMode == Release1TheEnvelopeAssignmentMode.Primary) ? (mission.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) && correlation.TransitionKind == Release1TransitionKind.MissionActivated) ?? string.Empty) : assignment.AuthorizationCorrelationId); return !string.IsNullOrEmpty(authorization); } private bool TryReconcileExecutedConsumptionEffect(Release1MissionRecord mission) { Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State?.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == ConsumptionEffectId(mission.Attempt)); if ((object)release1NativeEffectJournalEntry == null) { return false; } if (release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Prepared) { return true; } if (release1NativeEffectJournalEntry.ExecutionBlocked) { return false; } Release1TheEnvelopeAssignment release1TheEnvelopeAssignment = _story.State.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == mission.Attempt); string closetGuid = release1NativeEffectJournalEntry.SourceIdentity ?? string.Empty; if ((object)release1TheEnvelopeAssignment == null || !Release1TheEnvelopeClosetPlan.TryParse(release1NativeEffectJournalEntry.AmountOrCargoIdentity, closetGuid, out Release1TheEnvelopeClosetPlan plan) || (object)plan == null || release1NativeEffectJournalEntry.Attempt != mission.Attempt || !string.Equals(release1NativeEffectJournalEntry.EffectKind, "CashTransfer", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.DestinationIdentity, "oc-the-envelope", StringComparison.Ordinal) || plan.ConsumedAmount != release1TheEnvelopeAssignment.AmountWholeDollars || !plan.Slots.All((Release1TheEnvelopePlannedSlot slot) => MatchesSlotBalance(closetGuid, slot.SlotIndex, slot.PostBalance))) { return false; } MarkConsumptionApplied(release1NativeEffectJournalEntry, plan.Slots.Sum((Release1TheEnvelopePlannedSlot slot) => slot.PostBalance)); return true; } private void ReportHoldOnce(int attempt, string message) { if (_reportedHolds.Add($"a{attempt}:{message}")) { _log($"The Envelope attempt {attempt} is holding: {message}"); } } private void ReleaseSlotLocks() { if (_lockedClosetGuid == null) { return; } int[] array = _lockedSlotIndexes.ToArray(); foreach (int num in array) { bool flag = false; try { flag = _world.TrySetHoldRoomSlotLocked(_lockedClosetGuid, num, locked: false) == Release1SmallCourtesyWorldMutationStatus.Succeeded; } catch { flag = false; } if (flag) { _lockedSlotIndexes.Remove(num); } } if (_lockedSlotIndexes.Count == 0) { _lockedClosetGuid = null; } } private Release1TheEnvelopeDepositStatus Converge() { Release1TheEnvelopeDepositStatus result = ReconcileDeposit(); TryQueueArthur(); return result; } private bool TryBeginConvergencePass() { long num = _story.State?.Revision ?? (-1); if (!TryReadGameHours(out var gameHours)) { _lastConvergenceGameMinute = null; _lastConvergenceRevision = num; return true; } double num2 = Math.Floor(gameHours * 60.0); double? lastConvergenceGameMinute = _lastConvergenceGameMinute; if (lastConvergenceGameMinute.HasValue && _lastConvergenceGameMinute.Value == num2 && _lastConvergenceRevision == num) { return false; } _lastConvergenceGameMinute = num2; _lastConvergenceRevision = num; return true; } } public enum Release1TheEnvelopeCardStage { Hidden, Offer, Review, PersistenceDeferred, DecisionRejected, AwaitingActivation, Active, Shortfall, Spread, Ambiguous, Completed } public sealed record Release1TheEnvelopeViewModel(bool Visible, Release1TheEnvelopeCardStage Stage, string Title, string Body, bool CanReview, bool CanAccept, bool CanDefer); public static class Release1TheEnvelopePresentation { public const string Title = "The Envelope"; public const string OfferText = "One last thing before the family meets to discuss your future. Payment. You think we just allow anyone off the street in our family for free?"; public const string QuestionText = "Are you taking it?"; public const string SourceText = "Your own fucking cash. You have to prove to us you are worth the effort."; public const string PaymentText = "We don't pay you. You pay us. You want in? This is the cost."; public const string ClosetStatusText = "Closet: any one storage at HQ"; public const string ActiveStatusText = "Leave the whole amount in the closet before the window closes."; public const string ShortfallStatusFormat = "Part of the envelope is there. ${0} more still needed."; public const string SpreadStatusText = "The amount is spread across more than one closet. Put it together."; public const string CompletedStatusText = "Complete. The envelope is delivered."; public const string AmbiguousStatusText = "Stopped on conflicting evidence. It will not retry."; public static int StackCount(double amountWholeDollars) { return (int)Math.Round(amountWholeDollars / 1000.0, MidpointRounding.AwayFromZero); } public static string ClosetTermsText(double amountWholeDollars) { return $"Closet: one closet in the storage room at HQ. All {StackCount(amountWholeDollars)} stacks in it."; } public static string Dollars(double amount) { return amount.ToString("0", CultureInfo.InvariantCulture); } public static Release1TheEnvelopeViewModel Build(Release1StoryState? state, Release1TheEnvelopeOfferStatus offerStatus, Release1TheEnvelopeQuote? reviewedQuote, Release1TheEnvelopeProgress? progress, string? feedback = null, Release1TheEnvelopeCardStage? feedbackStage = null) { if (!string.IsNullOrWhiteSpace(feedback)) { return Visible(feedbackStage ?? Release1TheEnvelopeCardStage.PersistenceDeferred, Copy(feedback)); } if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Hidden(); } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; Release1TheEnvelopeAssignmentMode? expectedMode = Release1TheEnvelopeMissionService.ExpectedAssignmentMode(mission.State); Release1TheEnvelopeAssignment release1TheEnvelopeAssignment = state.TheEnvelopeAssignments.SingleOrDefault((Release1TheEnvelopeAssignment candidate) => candidate.Attempt == mission.Attempt && (!expectedMode.HasValue || candidate.Mode == expectedMode.Value)); if ((object)release1TheEnvelopeAssignment == null) { if ((object)reviewedQuote != null) { return Terms(reviewedQuote.Assignment, reviewedQuote.DeadlineDurationHours); } if (offerStatus == Release1TheEnvelopeOfferStatus.Available) { return new Release1TheEnvelopeViewModel(Visible: true, Release1TheEnvelopeCardStage.Offer, "The Envelope", Copy("One last thing before the family meets to discuss your future. Payment. You think we just allow anyone off the street in our family for free?"), CanReview: true, CanAccept: false, CanDefer: false); } return Hidden(); } if (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.the-envelope" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked)) { return Active(release1TheEnvelopeAssignment, mission, Release1TheEnvelopeCardStage.Ambiguous, "Stopped on conflicting evidence. It will not retry."); } if (mission.State == Release1MissionState.Satisfied) { return Active(release1TheEnvelopeAssignment, mission, Release1TheEnvelopeCardStage.Completed, "Complete. The envelope is delivered."); } if (mission.State == Release1MissionState.Accepted) { return Active(release1TheEnvelopeAssignment, mission, Release1TheEnvelopeCardStage.AwaitingActivation, "Leave the whole amount in the closet before the window closes."); } if ((object)progress != null && progress.SpreadNoticed) { return Active(release1TheEnvelopeAssignment, mission, Release1TheEnvelopeCardStage.Spread, "The amount is spread across more than one closet. Put it together."); } double? num = progress?.LastShortfallNoticed; if (num.HasValue) { double valueOrDefault = num.GetValueOrDefault(); return Active(release1TheEnvelopeAssignment, mission, Release1TheEnvelopeCardStage.Shortfall, $"Part of the envelope is there. ${Dollars(valueOrDefault)} more still needed."); } return Active(release1TheEnvelopeAssignment, mission, Release1TheEnvelopeCardStage.Active, "Leave the whole amount in the closet before the window closes."); } private static Release1TheEnvelopeViewModel Terms(Release1TheEnvelopeAssignment assignment, double? deadlineHours) { return new Release1TheEnvelopeViewModel(Visible: true, Release1TheEnvelopeCardStage.Review, "The Envelope", Copy(Release1PlayerCopy.JoinLines(Release1PlayerCopy.AttemptHeader(assignment.Mode, assignment.Attempt), $"${Dollars(assignment.AmountWholeDollars)} in cash, {StackCount(assignment.AmountWholeDollars)} stacks.", ClosetTermsText(assignment.AmountWholeDollars), "Deadline: " + Release1PlayerCopy.Deadline(deadlineHours), "We don't pay you. You pay us. You want in? This is the cost.", "Your own fucking cash. You have to prove to us you are worth the effort.", "Are you taking it?")), CanReview: false, CanAccept: true, CanDefer: true); } private static Release1TheEnvelopeViewModel Active(Release1TheEnvelopeAssignment assignment, Release1MissionRecord mission, Release1TheEnvelopeCardStage stage, string status) { return new Release1TheEnvelopeViewModel(Visible: true, stage, "The Envelope", Copy(string.Join("\n", "Status: " + status, "Amount: $" + Dollars(assignment.AmountWholeDollars) + " in cash", "Closet: any one storage at HQ", "Deadline: " + Release1PlayerCopy.Deadline(DeadlineDuration(mission)), "We don't pay you. You pay us. You want in? This is the cost.")), CanReview: false, CanAccept: false, CanDefer: false); } private static double? DeadlineDuration(Release1MissionRecord mission) { if (mission.DeadlineGameTimeHours.HasValue && mission.AcceptedGameTimeHours.HasValue) { return mission.DeadlineGameTimeHours.Value - mission.AcceptedGameTimeHours.Value; } return null; } private static Release1TheEnvelopeViewModel Visible(Release1TheEnvelopeCardStage stage, string body) { return new Release1TheEnvelopeViewModel(Visible: true, stage, "The Envelope", body, CanReview: false, CanAccept: false, CanDefer: false); } private static Release1TheEnvelopeViewModel Hidden() { return new Release1TheEnvelopeViewModel(Visible: false, Release1TheEnvelopeCardStage.Hidden, "The Envelope", string.Empty, CanReview: false, CanAccept: false, CanDefer: false); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1TheEnvelopePresenter : IDisposable { private readonly Release1TheEnvelopeMissionService _service; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private string? _feedback; private Release1TheEnvelopeCardStage? _feedbackStage; private bool _disposed; public Release1TheEnvelopeViewModel View { get { if (!_disposed) { return Release1TheEnvelopePresentation.Build(_story.State, _service.OfferStatus, _service.ReviewedQuote, CurrentProgress(), _feedback, _feedbackStage); } return Release1TheEnvelopePresentation.Build(null, Release1TheEnvelopeOfferStatus.Disposed, null, null); } } public Release1TheEnvelopePresenter(Release1TheEnvelopeMissionService service, Release1StoryRuntimeService story, Action? log = null) { _service = service ?? throw new ArgumentNullException("service"); _story = story ?? throw new ArgumentNullException("story"); _log = log; } public Release1TheEnvelopeReviewResult TryReview() { Release1TheEnvelopeReviewResult release1TheEnvelopeReviewResult = _service.TryReview(); _feedback = ((release1TheEnvelopeReviewResult.Status == Release1TheEnvelopeReviewStatus.Ready) ? null : release1TheEnvelopeReviewResult.Message); _feedbackStage = ((_feedback == null) ? ((Release1TheEnvelopeCardStage?)null) : new Release1TheEnvelopeCardStage?(Release1TheEnvelopeCardStage.DecisionRejected)); if (_feedback != null) { _log?.Invoke($"The Envelope review failed: {release1TheEnvelopeReviewResult.Status}, {release1TheEnvelopeReviewResult.Message}"); } return release1TheEnvelopeReviewResult; } public Release1TheEnvelopeDecisionResult TryAccept() { return Handle(_service.TryAccept()); } public Release1TheEnvelopeDecisionResult TryDefer() { return Handle(_service.TryDefer()); } public void ClearFeedback() { _feedback = null; _feedbackStage = null; } public void Dispose() { _feedback = null; _feedbackStage = null; _disposed = true; } private Release1TheEnvelopeProgress? CurrentProgress() { Release1StoryState state = _story.State; if ((object)state == null) { return null; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.the-envelope")]; return state.TheEnvelopeProgress.SingleOrDefault((Release1TheEnvelopeProgress progress) => progress.Attempt == mission.Attempt); } private Release1TheEnvelopeDecisionResult Handle(Release1TheEnvelopeDecisionResult result) { Release1TheEnvelopeDecisionStatus status = result.Status; if ((uint)status <= 2u) { _feedback = null; _feedbackStage = null; } else { _feedback = result.Message; _feedbackStage = ((result.Status == Release1TheEnvelopeDecisionStatus.PersistenceDeferred) ? Release1TheEnvelopeCardStage.PersistenceDeferred : Release1TheEnvelopeCardStage.DecisionRejected); _log?.Invoke($"The Envelope decision failed: {result.Status}, {result.Message}"); } return result; } } public sealed class Release1TheEnvelopeQuest : Quest { public const string DisplayTitle = "The Envelope"; private readonly QuestEntry[] _entries; protected override string Title => Release1PlayerCopy.Normalize("The Envelope"); protected override string Description => Release1PlayerCopy.Normalize("The Envelope"); public Release1TheEnvelopeQuest() { string text = Release1PlayerCopy.Normalize("The Envelope"); _entries = (QuestEntry[])(object)new QuestEntry[1] { ((Quest)this).AddEntry(text, (Vector3?)null) }; } public void Project(Release1DesiredQuest quest) { ArgumentNullException.ThrowIfNull(quest, "quest"); if (quest.Entries.Count != _entries.Length) { throw new InvalidOperationException($"Release 1 The Envelope quest desired entry count ({quest.Entries.Count}) does not match the quest's native entry count ({_entries.Length})."); } for (int i = 0; i < _entries.Length; i++) { ApplyEntry(_entries[i], quest.Entries[i]); } } public IReadOnlyList ReadEntryStates() { return _entries.Select((QuestEntry entry) => MapState(entry.State)).ToArray(); } private static void ApplyEntry(QuestEntry entry, Release1DesiredEntry desired) { //IL_0035: 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) entry.Title = Release1PlayerCopy.Normalize(desired.Text); Release1WorldPoint marker = desired.Marker; entry.POIPosition = (Vector3)(((object)marker != null) ? new Vector3(marker.X, marker.Y, marker.Z) : Vector3.zero); switch (Release1NativePresentationSupport.ClassifyEntryAction(desired.State)) { case Release1QuestEntryProjectionAction.Begin: entry.Begin(); break; case Release1QuestEntryProjectionAction.Complete: entry.Complete(); break; default: entry.SetState((QuestState)0); break; } } private static Release1DesiredEntryState MapState(QuestState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 1) { if ((int)state == 2) { return Release1DesiredEntryState.Complete; } return Release1DesiredEntryState.Inactive; } return Release1DesiredEntryState.Active; } } public sealed class Release1TransitionPublisherService : IDisposable { private const string IntroAcceptedReceipt = "post-benzies-intro-accepted-v1"; private const string IntroDeferredReceipt = "post-benzies-intro-deferred-v1"; private const string NellPhoneReceipt = "intro-contact-v1"; private static readonly string[] NellIntroStages = new string[2] { "You have made some changes around town. My associates noticed.", "There is a small courtesy you can do for us. I will send the details." }; private static readonly TimeSpan DefaultDeadline = TimeSpan.FromMinutes(10.0); private static readonly TimeSpan DefaultPollInterval = TimeSpan.FromSeconds(1.0); private readonly IRelease1PostBenziesUnlockReader _reader; private readonly Release1StoryRuntimeService _story; private readonly Release1PhoneCallService _phone; private readonly Action _log; private readonly Func _utcNow; private readonly TimeSpan _deadline; private readonly TimeSpan _pollInterval; private readonly bool _publishIntroCall; private DateTime _startedUtc; private DateTime _nextPollUtc; private bool _loadCycleActive; private bool _observing; private bool _hasEligibleContext; private Release1StoryHostContextSnapshot _eligibleContext; private bool _phoneAttemptedThisEpoch; private bool _disposed; public bool PromptVisible { get; private set; } public bool EligibilityObserving => _observing; public Release1TransitionPublisherService(IRelease1PostBenziesUnlockReader reader, Release1StoryRuntimeService story, Release1PhoneCallService phone, Action? log = null, Func? utcNow = null, TimeSpan? deadline = null, TimeSpan? pollInterval = null, bool publishIntroCall = true) { _reader = reader ?? throw new ArgumentNullException("reader"); _story = story ?? throw new ArgumentNullException("story"); _phone = phone ?? throw new ArgumentNullException("phone"); _log = log ?? ((Action)delegate { }); _utcNow = utcNow ?? ((Func)(() => DateTime.UtcNow)); _deadline = deadline ?? DefaultDeadline; _pollInterval = pollInterval ?? DefaultPollInterval; _publishIntroCall = publishIntroCall; if (_deadline <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException("deadline"); } if (_pollInterval <= TimeSpan.Zero) { throw new ArgumentOutOfRangeException("pollInterval"); } } public void OnLoadComplete() { if (!_disposed && !_loadCycleActive) { _loadCycleActive = true; _nextPollUtc = (_startedUtc = _utcNow()); PromptVisible = false; _hasEligibleContext = false; _eligibleContext = default(Release1StoryHostContextSnapshot); _phoneAttemptedThisEpoch = false; _observing = true; } } public void OnPreLoad() { _loadCycleActive = false; _observing = false; PromptVisible = false; _hasEligibleContext = false; _eligibleContext = default(Release1StoryHostContextSnapshot); _phoneAttemptedThisEpoch = false; } public void Update() { if (_disposed || !_observing) { return; } DateTime dateTime = _utcNow(); if (dateTime - _startedUtc >= _deadline) { _observing = false; } else { if (dateTime < _nextPollUtc) { return; } _nextPollUtc = dateTime + _pollInterval; if (_story.Phase == Release1StoryRuntimePhase.AwaitingLoad) { _story.OnLoadComplete(); } if (!_story.TryGetActiveContext(out var snapshot, out var rejectReason)) { if ((uint)(rejectReason - 12) <= 1u) { _observing = false; } return; } Release1PostBenziesUnlockReadStatus release1PostBenziesUnlockReadStatus; Release1PostBenziesUnlockSnapshot snapshot2; try { release1PostBenziesUnlockReadStatus = _reader.TryRead(out snapshot2); } catch (Exception ex) { _log("Release 1 eligibility observation faulted: " + ex.GetType().Name); _observing = false; return; } if ((uint)(release1PostBenziesUnlockReadStatus - 2) <= 1u) { return; } _observing = false; if (release1PostBenziesUnlockReadStatus != Release1PostBenziesUnlockReadStatus.Unlocked || snapshot2.CartelStatus != Release1CartelStatus.Defeated || !SameContext(snapshot, snapshot2.HostContext)) { return; } _eligibleContext = snapshot2.HostContext; _hasEligibleContext = true; bool promptVisible; switch (_story.State?.RelationshipState ?? Release1RelationshipState.Unstarted) { case Release1RelationshipState.Accepted: { Release1StoryState state = _story.State; if ((object)state != null && _story.LastPersistedRevision >= state.Revision) { TryPublishNellOnce(snapshot2.HostContext); } return; } case Release1RelationshipState.Unstarted: case Release1RelationshipState.Deferred: promptVisible = true; break; default: promptVisible = false; break; } PromptVisible = promptVisible; } } public bool TryAccept() { if (!TryReadPromptContext(out var context)) { return false; } Release1RelationshipState release1RelationshipState = _story.State?.RelationshipState ?? Release1RelationshipState.Unstarted; if ((uint)release1RelationshipState > 1u) { return false; } Release1StoryRuntimeCommandResult result = _story.TryExecuteDurably(CreateIntroCommand(context, Release1TransitionKind.IntroAccepted, "post-benzies-intro-accepted-v1")); if (!IsDurablyAccepted(result)) { return false; } PromptVisible = false; TryPublishNellOnce(context); return true; } public bool TryDefer() { if (!TryReadPromptContext(out var context)) { return false; } Release1StoryState? state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Deferred) { PromptVisible = false; return true; } if ((object)_story.State != null && _story.State.RelationshipState != Release1RelationshipState.Unstarted) { return false; } Release1StoryRuntimeCommandResult result = _story.TryExecuteDurably(CreateIntroCommand(context, Release1TransitionKind.IntroDeferred, "post-benzies-intro-deferred-v1")); if (!IsDurablyAccepted(result)) { return false; } PromptVisible = false; return true; } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; } } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private bool TryReadPromptContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (_disposed || !PromptVisible || !_hasEligibleContext) { return false; } if (!_story.TryGetActiveContext(out var snapshot, out var _) || !SameContext(snapshot, _eligibleContext)) { return false; } context = snapshot; return true; } private bool IsDurablyAccepted(Release1StoryRuntimeCommandResult result) { if (result.Accepted && (object)result.State != null) { return result.State.Revision == _story.LastPersistedRevision; } return false; } private void TryPublishNellOnce(Release1StoryHostContextSnapshot context) { if (!_publishIntroCall || _phoneAttemptedThisEpoch) { return; } _phoneAttemptedThisEpoch = true; try { _phone.TryQueue(CreateNellRequest(context)); } catch (Exception ex) { _log("Release 1 Nell request publication failed closed: " + ex.GetType().Name); } } private static Release1StoryCommand CreateIntroCommand(Release1StoryHostContextSnapshot context, Release1TransitionKind transition, string receipt) { return new Release1StoryCommand(context.SessionEpoch, context.LoadEpoch, context.PlayerId, "release1.intro", 0, transition, receipt, Release1LogicalCorrelation.Create(context.PlayerId, "release1.intro", 0, transition, receipt).Value); } private static Release1PhoneCallRequest CreateNellRequest(Release1StoryHostContextSnapshot context) { return Release1PhoneCallRequest.Create(context, "release1.small-courtesy", 1, Release1PhoneCallCorrelation.Create(context.PlayerId, "release1.small-courtesy", 1, Release1PhoneCallRole.Nell, "intro-contact-v1"), Release1PhoneCallRole.Nell, "Nell Grey", NellIntroStages); } } public sealed class Release1WrongAddressComposition : IDisposable { private bool _disposed; public Release1WrongAddressMissionService Service { get; } public Release1WrongAddressPresenter Presenter { get; } public Release1WrongAddressComposition(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService phone, Action? log = null) { ArgumentNullException.ThrowIfNull(story, "story"); ArgumentNullException.ThrowIfNull(world, "world"); ArgumentNullException.ThrowIfNull(phone, "phone"); Service = new Release1WrongAddressMissionService(story, world, phone, log); Presenter = new Release1WrongAddressPresenter(Service, story, log); } public void OnLoadComplete() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnLoadComplete(); } } public void OnPreLoad() { if (!_disposed) { Presenter.ClearFeedback(); Service.OnPreLoad(); } } public void OnSaveStart() { if (!_disposed) { Service.OnSaveStart(); } } public void OnSaveComplete() { if (!_disposed) { Service.OnSaveComplete(); } } public void Update() { if (!_disposed) { Service.Update(); } } public void Dispose() { if (!_disposed) { _disposed = true; Presenter.Dispose(); Service.Dispose(); } } } public enum Release1WrongAddressOfferStatus { Inactive, Available, Ineligible, Pending, Saving, NoDiscoveredProduct, InsufficientEmptyDeadDrops, Unavailable, Dismissed, Disposed } public enum Release1WrongAddressReviewStatus { Ready, Inactive, Ineligible, Pending, Saving, NoDiscoveredProduct, InsufficientEmptyDeadDrops, Unavailable, Dismissed, Disposed } public enum Release1WrongAddressDecisionStatus { Accepted, Deferred, Dismissed, ReviewRequired, QuoteChanged, InsufficientEmptyDeadDrops, PersistenceDeferred, Rejected, Inactive, Disposed } public enum Release1WrongAddressStageStatus { NoWork, Staged, AlreadyStaged, CustodyInferred, Held, Unavailable, Rejected } public enum Release1WrongAddressDeliveryStatus { NoWork, Paid, AwaitingAppliedSave, Committed, Ambiguous, Rejected } public sealed record Release1WrongAddressQuote(Release1WrongAddressAssignment Assignment, double? DeadlineDurationHours) { public void Validate() { Assignment.Validate(); if (Assignment.Mode == Release1WrongAddressAssignmentMode.Recovery) { if (DeadlineDurationHours.HasValue) { throw new ArgumentException("Recovery quotes cannot have a deadline.", "DeadlineDurationHours"); } } else if (DeadlineDurationHours.GetValueOrDefault() != 24.0) { throw new ArgumentException("Timed Wrong Address stages require a 24-hour duration.", "DeadlineDurationHours"); } } } public sealed record Release1WrongAddressReviewResult(Release1WrongAddressReviewStatus Status, Release1WrongAddressQuote? Quote, string Message); public sealed record Release1WrongAddressDecisionResult(Release1WrongAddressDecisionStatus Status, string Message); public sealed class Release1WrongAddressMissionService : IDisposable { private const string TermsVersion = "wrong-address-v1"; private const double TimedStageDurationHours = 24.0; private const string EffectScope = "oc-wrong-address"; private const string RewardDestination = "player-cash"; private const string ArthurCallerLabel = "Arthur Selby"; private static readonly string[] ArthurCleanStages = new string[3] { "Oi! This is Arthur Selby. You will not have heard of me, which is how I prefer it.", "You cleaned up someone else's fuckup. Thank god for that.", "Nell has my number. Keep doing quiet work and you will hear it again." }; private static readonly string[] ArthurWarningStages = new string[3] { "I am told the package is still sitting where it should not be. Stop fucking around or I will have to make a visit.", "Nell has arranged one more run. You have twenty four hours from the moment you accept it.", "Do not fucking make her ask twice or you will be hearing from me." }; private readonly Release1StoryRuntimeService _story; private readonly IRelease1SmallCourtesyWorld _world; private readonly Release1PhoneCallService? _phone; private readonly Action _log; private bool _loadActive; private bool _saving; private bool _dismissed; private bool _disposed; private Release1WrongAddressQuote? _reviewedQuote; private readonly HashSet _reportedHolds = new HashSet(StringComparer.Ordinal); private IRelease1SmallCourtesyDropSubscription? _dropSubscription; private string? _boundDropGuid; private string? _lockedDropGuid; private int _lockedSlotIndex = -1; public Release1WrongAddressOfferStatus OfferStatus { get; private set; } public Release1WrongAddressQuote? ReviewedQuote => _reviewedQuote; public Release1WrongAddressMissionService(Release1StoryRuntimeService story, IRelease1SmallCourtesyWorld world, Release1PhoneCallService? phone = null, Action? log = null) { _story = story ?? throw new ArgumentNullException("story"); _world = world ?? throw new ArgumentNullException("world"); _phone = phone; _log = log ?? ((Action)delegate { }); } public void OnLoadComplete() { if (!_disposed && !_loadActive) { _loadActive = true; _saving = false; _dismissed = false; _reviewedQuote = null; _reportedHolds.Clear(); RefreshOfferStatus(); Converge(); } } public void OnPreLoad() { if (!_disposed) { ReleaseSlotLock(); DisposeDropSubscription(); _loadActive = false; _saving = false; _dismissed = false; _reviewedQuote = null; _reportedHolds.Clear(); OfferStatus = Release1WrongAddressOfferStatus.Inactive; } } public void OnSaveStart() { if (!_disposed && _loadActive) { _saving = true; OfferStatus = Release1WrongAddressOfferStatus.Saving; } } public void OnSaveComplete() { if (!_disposed && _loadActive) { _saving = false; RefreshOfferStatus(); Converge(); } } public void Update() { if (_disposed || !_loadActive || _saving) { return; } Release1StoryHostContextSnapshot context; Release1WrongAddressReviewStatus release1WrongAddressReviewStatus = ReadMatchingContext(out context); if (release1WrongAddressReviewStatus != Release1WrongAddressReviewStatus.Ready) { OfferStatus = Map(release1WrongAddressReviewStatus); return; } Release1StoryState state = _story.State; if ((object)state != null && state.RelationshipState == Release1RelationshipState.Accepted) { Release1MissionRecord release1MissionRecord = Mission(state); Release1MissionState state2 = release1MissionRecord.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive) ? true : false); if (flag && release1MissionRecord.DeadlineGameTimeHours.HasValue && TryReadGameHours(out var gameHours) && gameHours >= release1MissionRecord.DeadlineGameTimeHours.Value && !HasConsumptionEffect(release1MissionRecord.Attempt)) { Release1TransitionKind transition = ((release1MissionRecord.State == Release1MissionState.Active) ? Release1TransitionKind.RequiredFailure : Release1TransitionKind.MakeGoodFailed); string receipt = $"wrong-address-deadline-{DeadlineToken(release1MissionRecord.State)}-v1-a{release1MissionRecord.Attempt}"; _story.TryExecuteDurably(CreateCommand(transition, release1MissionRecord.Attempt, receipt, null, null, null)); RefreshOfferStatus(); } Converge(); } } public Release1WrongAddressReviewResult TryReview() { if (_disposed) { return Review(Release1WrongAddressReviewStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Review(Release1WrongAddressReviewStatus.Inactive, "No load epoch is active."); } if (_saving) { return Review(Release1WrongAddressReviewStatus.Saving, "Mission review is deferred during saving."); } if (_dismissed) { return Review(Release1WrongAddressReviewStatus.Dismissed, "Offer was dismissed for this load."); } Release1WrongAddressQuote quote; Release1WrongAddressReviewStatus release1WrongAddressReviewStatus = TryBuildQuote(out quote); if (release1WrongAddressReviewStatus != Release1WrongAddressReviewStatus.Ready) { _reviewedQuote = null; OfferStatus = Map(release1WrongAddressReviewStatus); return Review(release1WrongAddressReviewStatus, Message(release1WrongAddressReviewStatus)); } _reviewedQuote = quote; OfferStatus = Release1WrongAddressOfferStatus.Available; return new Release1WrongAddressReviewResult(Release1WrongAddressReviewStatus.Ready, quote, "Wrong Address terms are ready for review."); } public Release1WrongAddressDecisionResult TryAccept() { if (_disposed) { return Decision(Release1WrongAddressDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _dismissed) { return Decision(Release1WrongAddressDecisionStatus.Inactive, "No active offer is available."); } if (_saving) { return Decision(Release1WrongAddressDecisionStatus.PersistenceDeferred, "Acceptance is deferred during saving."); } if ((object)_reviewedQuote == null) { return Decision(Release1WrongAddressDecisionStatus.ReviewRequired, "Review the current terms before accepting."); } Release1WrongAddressQuote quote; Release1WrongAddressReviewStatus release1WrongAddressReviewStatus = TryBuildQuote(out quote); switch (release1WrongAddressReviewStatus) { case Release1WrongAddressReviewStatus.InsufficientEmptyDeadDrops: _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1WrongAddressDecisionStatus.InsufficientEmptyDeadDrops, Message(release1WrongAddressReviewStatus)); case Release1WrongAddressReviewStatus.Ready: if (!(quote != _reviewedQuote)) { if (!TryReadGameHours(out var gameHours)) { return Decision(Release1WrongAddressDecisionStatus.Rejected, "Canonical game time was unavailable."); } Release1WrongAddressAssignment assignment = quote.Assignment; Release1StoryCommand command = CreateCommand(assignment.Mode switch { Release1WrongAddressAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1WrongAddressAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1WrongAddressAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }, assignment.Attempt, AcceptanceReceipt(assignment.Mode, assignment.Attempt), (assignment.Mode == Release1WrongAddressAssignmentMode.Primary) ? "wrong-address-v1" : null, gameHours, (assignment.Mode == Release1WrongAddressAssignmentMode.Recovery) ? ((double?)null) : new double?(gameHours + 24.0)); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteWrongAddressAcceptanceDurably(command, assignment); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1WrongAddressDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1WrongAddressDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } _reviewedQuote = null; RefreshOfferStatus(); Converge(); return Decision(Release1WrongAddressDecisionStatus.Accepted, "Wrong Address stage was accepted."); } goto default; default: _reviewedQuote = null; RefreshOfferStatus(); return Decision(Release1WrongAddressDecisionStatus.QuoteChanged, "The available product, package, or drops changed; review again."); } } public Release1WrongAddressDecisionResult TryDefer() { if (_disposed) { return Decision(Release1WrongAddressDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive || _saving || ReadMatchingContext(out var _) != Release1WrongAddressReviewStatus.Ready) { return Decision(Release1WrongAddressDecisionStatus.Inactive, "No active offer is available."); } Release1StoryState state = _story.State; Release1MissionRecord release1MissionRecord = (((object)state == null) ? null : Mission(state)); if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted || (object)release1MissionRecord == null || release1MissionRecord.State != Release1MissionState.Offered) { return TryDismiss(); } string receipt = $"wrong-address-defer-v1-a{release1MissionRecord.Attempt}"; Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecuteDurably(CreateCommand(Release1TransitionKind.MissionDeferred, release1MissionRecord.Attempt, receipt, null, null, null)); if (release1StoryRuntimeCommandResult.Status == Release1StoryCommandStatus.DeferredSaving) { return Decision(Release1WrongAddressDecisionStatus.PersistenceDeferred, release1StoryRuntimeCommandResult.Message); } if (!release1StoryRuntimeCommandResult.Accepted) { return Decision(Release1WrongAddressDecisionStatus.Rejected, release1StoryRuntimeCommandResult.Message); } DismissForLoad(); return Decision(Release1WrongAddressDecisionStatus.Deferred, "Wrong Address was deferred until a later load."); } public Release1WrongAddressDecisionResult TryDismiss() { if (_disposed) { return Decision(Release1WrongAddressDecisionStatus.Disposed, "Mission service was disposed."); } if (!_loadActive) { return Decision(Release1WrongAddressDecisionStatus.Inactive, "No load epoch is active."); } DismissForLoad(); return Decision(Release1WrongAddressDecisionStatus.Dismissed, "Wrong Address was dismissed for this load."); } public void Dispose() { if (!_disposed) { OnPreLoad(); _disposed = true; OfferStatus = Release1WrongAddressOfferStatus.Disposed; } } private Release1WrongAddressReviewStatus TryBuildQuote(out Release1WrongAddressQuote? quote) { try { return TryBuildQuoteCore(out quote); } catch { quote = null; return Release1WrongAddressReviewStatus.Unavailable; } } private Release1WrongAddressReviewStatus TryBuildQuoteCore(out Release1WrongAddressQuote? quote) { quote = null; Release1StoryHostContextSnapshot context; Release1WrongAddressReviewStatus release1WrongAddressReviewStatus = ReadMatchingContext(out context); if (release1WrongAddressReviewStatus != Release1WrongAddressReviewStatus.Ready) { return release1WrongAddressReviewStatus; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1WrongAddressReviewStatus.Ineligible; } if (state.Missions[Release1MissionCatalog.IndexOf("release1.small-courtesy")].State != Release1MissionState.Satisfied) { return Release1WrongAddressReviewStatus.Ineligible; } Release1MissionRecord release1MissionRecord = Mission(state); Release1WrongAddressAssignmentMode? release1WrongAddressAssignmentMode = release1MissionRecord.State switch { Release1MissionState.Offered => Release1WrongAddressAssignmentMode.Primary, Release1MissionState.MakeGoodOffered => Release1WrongAddressAssignmentMode.MakeGood, Release1MissionState.RecoveryAvailable => Release1WrongAddressAssignmentMode.Recovery, _ => null, }; if (!release1WrongAddressAssignmentMode.HasValue) { return Release1WrongAddressReviewStatus.Ineligible; } int attempt = ((release1WrongAddressAssignmentMode == Release1WrongAddressAssignmentMode.Primary) ? release1MissionRecord.Attempt : (release1MissionRecord.Attempt + 1)); Release1TransitionKind transitionKind = release1WrongAddressAssignmentMode switch { Release1WrongAddressAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1WrongAddressAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1WrongAddressAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new InvalidOperationException("Assignment mode was not defined."), }; string value = Release1LogicalCorrelation.Create(context.PlayerId, "release1.wrong-address", attempt, transitionKind, AcceptanceReceipt(release1WrongAddressAssignmentMode.Value, attempt)).Value; Release1SmallCourtesyPackageKind kind = ((release1WrongAddressAssignmentMode != Release1WrongAddressAssignmentMode.Primary) ? Release1SmallCourtesyPackageKind.Jar : Release1SmallCourtesyPackageKind.Brick); if (_world.TryReadPackaging(kind, out Release1SmallCourtesyPackagingCandidate packaging) != Release1SmallCourtesyWorldReadStatus.Ready) { return Release1WrongAddressReviewStatus.Unavailable; } try { packaging.Validate(); } catch (ArgumentException) { return Release1WrongAddressReviewStatus.Unavailable; } IReadOnlyList products; if (release1WrongAddressAssignmentMode == Release1WrongAddressAssignmentMode.Primary) { Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadProducts(out products); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } } else { Release1WrongAddressAssignment release1WrongAddressAssignment = state.WrongAddressAssignments.OrderBy((Release1WrongAddressAssignment candidate) => candidate.Attempt).FirstOrDefault(); if ((object)release1WrongAddressAssignment == null) { return Release1WrongAddressReviewStatus.Ineligible; } products = new Release1SmallCourtesyProductCandidate[1] { new Release1SmallCourtesyProductCandidate(release1WrongAddressAssignment.ProductId, release1WrongAddressAssignment.ProductName, 0.0, IsDiscovered: true) }; } IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = _world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus2); } if (!Release1WrongAddressAssignmentSelector.TrySelect(products, drops, release1WrongAddressAssignmentMode.Value, attempt, value, packaging.PackagingId, packaging.PackagingName, out Release1WrongAddressAssignment assignment, out Release1WrongAddressSelectionStatus status)) { return status switch { Release1WrongAddressSelectionStatus.NoDiscoveredProduct => Release1WrongAddressReviewStatus.NoDiscoveredProduct, Release1WrongAddressSelectionStatus.InsufficientEmptyDeadDrops => Release1WrongAddressReviewStatus.InsufficientEmptyDeadDrops, _ => Release1WrongAddressReviewStatus.Unavailable, }; } quote = new Release1WrongAddressQuote(assignment, (release1WrongAddressAssignmentMode == Release1WrongAddressAssignmentMode.Recovery) ? ((double?)null) : new double?(24.0)); quote.Validate(); return Release1WrongAddressReviewStatus.Ready; } private void RefreshOfferStatus() { if (_disposed) { OfferStatus = Release1WrongAddressOfferStatus.Disposed; } else if (!_loadActive) { OfferStatus = Release1WrongAddressOfferStatus.Inactive; } else if (_saving) { OfferStatus = Release1WrongAddressOfferStatus.Saving; } else if (_dismissed) { OfferStatus = Release1WrongAddressOfferStatus.Dismissed; } else { OfferStatus = Map(TryBuildQuote(out Release1WrongAddressQuote _)); } } private Release1WrongAddressReviewStatus ReadMatchingContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return Release1WrongAddressReviewStatus.Inactive; } try { Release1StoryHostContextSnapshot context2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _world.TryReadContext(out context2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return Map(release1SmallCourtesyWorldReadStatus); } if (!SameContext(snapshot, context2)) { return Release1WrongAddressReviewStatus.Inactive; } context = snapshot; return Release1WrongAddressReviewStatus.Ready; } catch { return Release1WrongAddressReviewStatus.Unavailable; } } private bool TryReadGameHours(out double gameHours) { gameHours = 0.0; try { if (_world.TryReadCanonicalTotalMinutes(out var totalMinutes) != Release1SmallCourtesyWorldReadStatus.Ready || !double.IsFinite(totalMinutes) || totalMinutes < 0.0) { return false; } gameHours = totalMinutes / 60.0; return double.IsFinite(gameHours); } catch { return false; } } private Release1StoryCommand CreateCommand(Release1TransitionKind transition, int attempt, string receipt, string? termsVersion, double? acceptedHours, double? deadlineHours) { if (!_story.TryGetActiveContext(out var snapshot, out var _)) { throw new InvalidOperationException("Story context was not active."); } return new Release1StoryCommand(snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId, "release1.wrong-address", attempt, transition, receipt, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.wrong-address", attempt, transition, receipt).Value, termsVersion, acceptedHours, deadlineHours); } private void DismissForLoad() { _dismissed = true; _reviewedQuote = null; OfferStatus = Release1WrongAddressOfferStatus.Dismissed; } private static Release1MissionRecord Mission(Release1StoryState state) { return state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; } private static string AcceptanceReceipt(Release1WrongAddressAssignmentMode mode, int attempt) { return $"wrong-address-{mode.ToString().ToLowerInvariant()}-accept-v1-a{attempt}"; } private static string DeadlineToken(Release1MissionState state) { if (state != Release1MissionState.Active) { return "make-good"; } return "primary"; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(Path.GetFullPath(left.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), Path.GetFullPath(right.ActiveSaveFolder).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar), StringComparison.OrdinalIgnoreCase); } return false; } private static Release1WrongAddressReviewResult Review(Release1WrongAddressReviewStatus status, string message) { return new Release1WrongAddressReviewResult(status, null, message); } private static Release1WrongAddressDecisionResult Decision(Release1WrongAddressDecisionStatus status, string message) { return new Release1WrongAddressDecisionResult(status, message); } private static Release1WrongAddressReviewStatus Map(Release1SmallCourtesyWorldReadStatus status) { if ((uint)(status - 1) <= 1u) { return Release1WrongAddressReviewStatus.Pending; } return Release1WrongAddressReviewStatus.Unavailable; } private static Release1WrongAddressOfferStatus Map(Release1WrongAddressReviewStatus status) { return status switch { Release1WrongAddressReviewStatus.Ready => Release1WrongAddressOfferStatus.Available, Release1WrongAddressReviewStatus.Inactive => Release1WrongAddressOfferStatus.Inactive, Release1WrongAddressReviewStatus.Ineligible => Release1WrongAddressOfferStatus.Ineligible, Release1WrongAddressReviewStatus.Pending => Release1WrongAddressOfferStatus.Pending, Release1WrongAddressReviewStatus.Saving => Release1WrongAddressOfferStatus.Saving, Release1WrongAddressReviewStatus.NoDiscoveredProduct => Release1WrongAddressOfferStatus.NoDiscoveredProduct, Release1WrongAddressReviewStatus.InsufficientEmptyDeadDrops => Release1WrongAddressOfferStatus.InsufficientEmptyDeadDrops, Release1WrongAddressReviewStatus.Dismissed => Release1WrongAddressOfferStatus.Dismissed, Release1WrongAddressReviewStatus.Disposed => Release1WrongAddressOfferStatus.Disposed, _ => Release1WrongAddressOfferStatus.Unavailable, }; } private static string Message(Release1WrongAddressReviewStatus status) { return status switch { Release1WrongAddressReviewStatus.NoDiscoveredProduct => "No discovered product is available for assignment.", Release1WrongAddressReviewStatus.InsufficientEmptyDeadDrops => "Two clear dead drops are required before a recovery run can be assigned.", Release1WrongAddressReviewStatus.Pending => "World eligibility is still pending.", Release1WrongAddressReviewStatus.Ineligible => "Wrong Address is not currently offerable.", Release1WrongAddressReviewStatus.Inactive => "The canonical story context is inactive.", _ => "Wrong Address world data is unavailable.", }; } public Release1WrongAddressStageStatus ReconcileStaging() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1WrongAddressReviewStatus.Ready) { return Release1WrongAddressStageStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord mission, out Release1WrongAddressAssignment assignment, out string _)) { return Release1WrongAddressStageStatus.NoWork; } Release1WrongAddressProgress release1WrongAddressProgress = Progress(assignment.Attempt); if ((object)release1WrongAddressProgress != null && release1WrongAddressProgress.Custody) { return Release1WrongAddressStageStatus.NoWork; } if (HasConsumptionEffect(mission.Attempt)) { return Release1WrongAddressStageStatus.NoWork; } if (!TryReadSlots(assignment.SourceDropGuid, out IReadOnlyList slots)) { return Release1WrongAddressStageStatus.Unavailable; } int matchedSlotIndex; switch (Release1ConsignmentClassifier.Classify(slots, Fingerprint(assignment), out matchedSlotIndex)) { case Release1ContainerObservation.ZeroLength: ReportHoldOnce(assignment.Attempt, "the source drop read back with zero slots; a zero-length read is not treated as empty."); return Release1WrongAddressStageStatus.Unavailable; case Release1ContainerObservation.ExactlyOne: if ((object)release1WrongAddressProgress != null && release1WrongAddressProgress.Staged) { return Release1WrongAddressStageStatus.AlreadyStaged; } if (!_story.TrySetWrongAddressStaged(assignment.Attempt).Accepted) { return Release1WrongAddressStageStatus.Rejected; } return Release1WrongAddressStageStatus.AlreadyStaged; default: ReportHoldOnce(assignment.Attempt, "the source drop holds something other than exactly the staged package; Wrong Address will not mutate it."); return Release1WrongAddressStageStatus.Held; case Release1ContainerObservation.Empty: { if ((object)release1WrongAddressProgress != null && release1WrongAddressProgress.Staged) { if (!_story.TrySetWrongAddressCustody(assignment.Attempt).Accepted) { return Release1WrongAddressStageStatus.Rejected; } return Release1WrongAddressStageStatus.CustodyInferred; } Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; string reason; try { release1SmallCourtesyWorldMutationStatus = _world.TryInsertPackagedProduct(assignment.SourceDropGuid, 0, assignment.ProductId, assignment.PackagingId, assignment.PackageQuantity, out reason); } catch (Exception ex) { ReportHoldOnce(assignment.Attempt, "staging threw " + ex.GetType().Name + "; Wrong Address will not retry blindly."); return Release1WrongAddressStageStatus.Held; } switch (release1SmallCourtesyWorldMutationStatus) { case Release1SmallCourtesyWorldMutationStatus.Unavailable: return Release1WrongAddressStageStatus.Unavailable; default: ReportHoldOnce(assignment.Attempt, "staging was refused: " + reason); return Release1WrongAddressStageStatus.Held; case Release1SmallCourtesyWorldMutationStatus.Succeeded: { if (!TryReadSlots(assignment.SourceDropGuid, out IReadOnlyList slots2)) { ReportHoldOnce(assignment.Attempt, "the staged package could not be read back after insertion."); return Release1WrongAddressStageStatus.Held; } if (Release1ConsignmentClassifier.Classify(slots2, Fingerprint(assignment), out matchedSlotIndex) != Release1ContainerObservation.ExactlyOne) { ReportHoldOnce(assignment.Attempt, "the staged package did not read back as the exact single fingerprint match in the drop."); return Release1WrongAddressStageStatus.Held; } if (!_story.TrySetWrongAddressStaged(assignment.Attempt).Accepted) { return Release1WrongAddressStageStatus.Rejected; } return Release1WrongAddressStageStatus.Staged; } } } } } public Release1WrongAddressStageStatus TryHandleDropClosed(string deadDropGuid) { if (_disposed || !_loadActive || _saving) { return Release1WrongAddressStageStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord _, out Release1WrongAddressAssignment assignment, out string _)) { return Release1WrongAddressStageStatus.NoWork; } bool flag = Progress(assignment.Attempt)?.Custody ?? false; string b = (flag ? assignment.HandoffDropGuid : assignment.SourceDropGuid); if (!string.Equals(deadDropGuid, b, StringComparison.Ordinal)) { return Release1WrongAddressStageStatus.NoWork; } Release1WrongAddressStageStatus result = Converge(); if (!flag) { return result; } return Release1WrongAddressStageStatus.NoWork; } private Release1WrongAddressStageStatus Converge() { Release1WrongAddressStageStatus result = ReconcileStaging(); ReconcileDelivery(); RefreshDropBinding(); TryQueueArthur(); return result; } private void TryQueueArthur() { if (_phone == null || _disposed || !_loadActive || _saving) { return; } Release1StoryState state = _story.State; if ((object)state == null || !_story.TryGetActiveContext(out var snapshot, out var _)) { return; } Release1MissionRecord mission = Mission(state); string receiptId; IReadOnlyList stageTexts; if (mission.State == Release1MissionState.Satisfied && mission.LastOutcome == Release1MissionOutcome.OnTime && mission.MakeGoodFailures == 0 && state.WrongAddressAssignments.Any((Release1WrongAddressAssignment assignment) => assignment.Attempt == mission.Attempt && assignment.Mode == Release1WrongAddressAssignmentMode.Primary)) { receiptId = "wa-clean-v1"; stageTexts = ArthurCleanStages; } else { if (mission.State != Release1MissionState.MakeGoodOffered || mission.LastOutcome != Release1MissionOutcome.RequiredFailure) { return; } receiptId = "wa-warning-v1"; stageTexts = ArthurWarningStages; } try { Release1PhoneCallRequest request = Release1PhoneCallRequest.Create(snapshot, "release1.wrong-address", mission.Attempt, Release1PhoneCallCorrelation.Create(snapshot.PlayerId, "release1.wrong-address", mission.Attempt, Release1PhoneCallRole.Arthur, receiptId), Release1PhoneCallRole.Arthur, "Arthur Selby", stageTexts, Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.wrong-address", mission.Attempt, Release1TransitionKind.MissionAccepted, "presentation-nell-wa-accepted-v1").Value); _phone.TryQueue(request); } catch (Exception ex) { _log("Wrong Address Arthur request was not published: " + ex.GetType().Name); } } public Release1WrongAddressDeliveryStatus ReconcileDelivery() { if (_disposed || !_loadActive || _saving || ReadMatchingContext(out var _) != Release1WrongAddressReviewStatus.Ready) { return Release1WrongAddressDeliveryStatus.NoWork; } Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Release1WrongAddressDeliveryStatus.NoWork; } Release1MissionRecord release1MissionRecord = Mission(state); int attempt = release1MissionRecord.Attempt; Release1WrongAddressAssignment release1WrongAddressAssignment = state.WrongAddressAssignments.SingleOrDefault((Release1WrongAddressAssignment candidate) => candidate.Attempt == attempt); if ((object)release1WrongAddressAssignment == null) { return Release1WrongAddressDeliveryStatus.NoWork; } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == ConsumptionEffectId(attempt)); if ((object)release1NativeEffectJournalEntry == null) { Release1WrongAddressProgress? release1WrongAddressProgress = Progress(attempt); if ((object)release1WrongAddressProgress == null || !release1WrongAddressProgress.Custody) { return Release1WrongAddressDeliveryStatus.NoWork; } if (!TryGetActiveStage(out Release1MissionRecord _, out Release1WrongAddressAssignment _, out string authorization)) { return Release1WrongAddressDeliveryStatus.NoWork; } return BeginDelivery(release1MissionRecord, release1WrongAddressAssignment, authorization); } return ContinueDelivery(release1MissionRecord, release1WrongAddressAssignment, release1NativeEffectJournalEntry); } private Release1WrongAddressDeliveryStatus BeginDelivery(Release1MissionRecord mission, Release1WrongAddressAssignment assignment, string authorization) { if (!TryReadSlots(assignment.HandoffDropGuid, out IReadOnlyList slots)) { return Release1WrongAddressDeliveryStatus.Rejected; } Release1SmallCourtesySlotSnapshot single; int num = Release1ConsignmentClassifier.CountMatches(slots, Fingerprint(assignment), out single); if (num == 0) { return Release1WrongAddressDeliveryStatus.NoWork; } if (num > 1) { ReportHoldOnce(assignment.Attempt, "the handoff drop holds more than one matching package; Wrong Address will consume none of them."); return Release1WrongAddressDeliveryStatus.Ambiguous; } Release1SmallCourtesyCargoIdentity release1SmallCourtesyCargoIdentity; try { release1SmallCourtesyCargoIdentity = new Release1SmallCourtesyCargoIdentity(assignment.ProductId, assignment.PackagingId, single.SlotIndex, single.Quantity, single.Quantity - 1, single.MonetaryValue); } catch (ArgumentException) { return Release1WrongAddressDeliveryStatus.Rejected; } Release1NativeEffectJournalEntry prepared = new Release1NativeEffectJournalEntry(ConsumptionEffectId(assignment.Attempt), "release1.wrong-address", assignment.Attempt, "CargoTransfer", assignment.HandoffDropGuid, "oc-wrong-address", release1SmallCourtesyCargoIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, _story.State.Revision + 1, null, authorization, ExecutionBlocked: false, mission.Revision); if (!_story.TryPrepareNativeEffect(prepared).Accepted) { return Release1WrongAddressDeliveryStatus.Rejected; } Release1NativeEffectJournalEntry effect = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == prepared.EffectId); if (!ConsumeOneUnit(assignment, release1SmallCourtesyCargoIdentity, effect)) { return Release1WrongAddressDeliveryStatus.Ambiguous; } return FinishDelivery(Mission(_story.State), assignment, effect, release1SmallCourtesyCargoIdentity, authorization); } private Release1WrongAddressDeliveryStatus ContinueDelivery(Release1MissionRecord mission, Release1WrongAddressAssignment assignment, Release1NativeEffectJournalEntry effect) { string authorizedStoryCorrelationId = effect.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId) || !Release1SmallCourtesyCargoIdentity.TryParse(effect.AmountOrCargoIdentity, out Release1SmallCourtesyCargoIdentity identity) || (object)identity == null || effect.Attempt != mission.Attempt || !string.Equals(effect.EffectKind, "CargoTransfer", StringComparison.Ordinal) || !string.Equals(effect.SourceIdentity, assignment.HandoffDropGuid, StringComparison.Ordinal) || !string.Equals(effect.DestinationIdentity, "oc-wrong-address", StringComparison.Ordinal) || !string.Equals(identity.ProductId, assignment.ProductId, StringComparison.Ordinal) || !string.Equals(identity.PackageId, assignment.PackagingId, StringComparison.Ordinal)) { return BlockConsumption(effect); } if (effect.ExecutionBlocked) { return Release1WrongAddressDeliveryStatus.Ambiguous; } if (effect.Phase == Release1NativeEffectPhase.Prepared) { if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot)) { return Release1WrongAddressDeliveryStatus.Rejected; } bool flag = MatchesCargoSlot(slot, identity, identity.PreQuantity); if (MatchesCargoSlot(slot, identity, identity.PostQuantity)) { if (!MarkConsumptionApplied(effect)) { return Release1WrongAddressDeliveryStatus.Rejected; } } else { if (!flag) { return BlockConsumption(effect); } if (!ConsumeOneUnit(assignment, identity, effect)) { return Release1WrongAddressDeliveryStatus.Ambiguous; } } } return FinishDelivery(mission, assignment, effect, identity, authorizedStoryCorrelationId); } private Release1WrongAddressDeliveryStatus FinishDelivery(Release1MissionRecord mission, Release1WrongAddressAssignment assignment, Release1NativeEffectJournalEntry effect, Release1SmallCourtesyCargoIdentity identity, string authorization) { long revision = _story.State.Revision; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = _story.State.NativeEffects.Single((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effect.EffectId); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { return Release1WrongAddressDeliveryStatus.Ambiguous; } if (!TryCompleteAndPrepareReward(release1NativeEffectJournalEntry, identity)) { return Release1WrongAddressDeliveryStatus.Rejected; } Release1WrongAddressDeliveryStatus release1WrongAddressDeliveryStatus = ReconcileReward(assignment.Attempt, revision); if (release1WrongAddressDeliveryStatus != Release1WrongAddressDeliveryStatus.Paid && release1WrongAddressDeliveryStatus != Release1WrongAddressDeliveryStatus.Committed && release1WrongAddressDeliveryStatus != Release1WrongAddressDeliveryStatus.AwaitingAppliedSave) { return release1WrongAddressDeliveryStatus; } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied && revision <= _story.LastPersistedRevision) { Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorization, revision); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(assignment.Attempt, "the consumption commit after a save was rejected: " + release1StoryRuntimeCommandResult.Message); } } return release1WrongAddressDeliveryStatus switch { Release1WrongAddressDeliveryStatus.Paid => Release1WrongAddressDeliveryStatus.Paid, Release1WrongAddressDeliveryStatus.Committed => Release1WrongAddressDeliveryStatus.Committed, _ => Release1WrongAddressDeliveryStatus.AwaitingAppliedSave, }; } private bool ConsumeOneUnit(Release1WrongAddressAssignment assignment, Release1SmallCourtesyCargoIdentity identity, Release1NativeEffectJournalEntry effect) { if (!TryAcquireSlotLock(assignment.HandoffDropGuid, identity.SlotIndex)) { BlockConsumption(effect); return false; } try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeSlotQuantity(assignment.HandoffDropGuid, identity.SlotIndex, -1); } catch { BlockConsumption(effect); return false; } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { BlockConsumption(effect); return false; } if (!TryReadSlot(assignment.HandoffDropGuid, identity.SlotIndex, out Release1SmallCourtesySlotSnapshot slot) || !MatchesCargoSlot(slot, identity, identity.PostQuantity)) { BlockConsumption(effect); return false; } return MarkConsumptionApplied(effect); } finally { ReleaseSlotLock(); } } private bool MarkConsumptionApplied(Release1NativeEffectJournalEntry effect) { return _story.TryMarkNativeEffectApplied(effect.EffectId, $"wrong-address-cargo-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted; } private bool TryCompleteAndPrepareReward(Release1NativeEffectJournalEntry consumption, Release1SmallCourtesyCargoIdentity identity) { Release1StoryState state = _story.State; if ((object)state == null) { return false; } Release1MissionRecord mission = Mission(state); Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry effect) => effect.EffectId == RewardEffectId(mission.Attempt)); if (mission.State == Release1MissionState.Satisfied) { return (object)release1NativeEffectJournalEntry != null; } Release1MissionState state2 = mission.State; bool flag = ((state2 == Release1MissionState.Active || state2 == Release1MissionState.MakeGoodActive || state2 == Release1MissionState.RecoveryActive) ? true : false); if (!flag || consumption.Phase != Release1NativeEffectPhase.Applied || consumption.ExecutionBlocked || consumption.Attempt != mission.Attempt || (object)release1NativeEffectJournalEntry != null || !TryReadCashBalance(out var balance)) { return false; } float? num = Release1RewardMath.QuoteWholeDollars(identity.DepositedMonetaryValue); if (num.HasValue) { float valueOrDefault = num.GetValueOrDefault(); Release1SmallCourtesyRewardIdentity release1SmallCourtesyRewardIdentity; try { release1SmallCourtesyRewardIdentity = new Release1SmallCourtesyRewardIdentity(balance, valueOrDefault, balance + valueOrDefault); } catch (ArgumentException) { return false; } if (!_story.TryGetActiveContext(out var snapshot, out var _)) { return false; } string receiptId = $"wrong-address-complete-v1-a{mission.Attempt}"; string value = Release1LogicalCorrelation.Create(snapshot.PlayerId, "release1.wrong-address", mission.Attempt, Release1TransitionKind.MissionCompleted, receiptId).Value; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = new Release1NativeEffectJournalEntry(RewardEffectId(mission.Attempt), "release1.wrong-address", mission.Attempt, "Reward", "oc-wrong-address", "player-cash", release1SmallCourtesyRewardIdentity.Serialize(), Release1NativeEffectPhase.Prepared, null, state.Revision + 1, null, value, ExecutionBlocked: false, mission.Revision); Guid sessionEpoch = snapshot.SessionEpoch; long loadEpoch = snapshot.LoadEpoch; string playerId = snapshot.PlayerId; int attempt = mission.Attempt; Release1CompletionTiming? completionTiming = ((mission.State != Release1MissionState.Active) ? Release1CompletionTiming.Late : Release1CompletionTiming.OnTime); string rewardAuthorizationReceiptId = $"wrong-address-reward-auth-v1-a{mission.Attempt}"; Release1NativeEffectJournalEntry preparedNativeEffect = release1NativeEffectJournalEntry2; Release1StoryCommand command = new Release1StoryCommand(sessionEpoch, loadEpoch, playerId, "release1.wrong-address", attempt, Release1TransitionKind.MissionCompleted, receiptId, value, null, null, null, completionTiming, rewardAuthorizationReceiptId, null, null, preparedNativeEffect); Release1StoryRuntimeCommandResult release1StoryRuntimeCommandResult = _story.TryExecute(command); if (!release1StoryRuntimeCommandResult.Accepted) { ReportHoldOnce(mission.Attempt, "completing the mission after the unit was consumed was rejected: " + release1StoryRuntimeCommandResult.Message); return false; } return true; } return false; } private Release1WrongAddressDeliveryStatus ReconcileReward(int attempt, long capturedRevision) { Release1StoryState? state = _story.State; Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = state?.NativeEffects.FirstOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == RewardEffectId(attempt)); if ((object)state == null || (object)release1NativeEffectJournalEntry == null) { return Release1WrongAddressDeliveryStatus.Rejected; } if (!Release1SmallCourtesyRewardIdentity.TryParse(release1NativeEffectJournalEntry.AmountOrCargoIdentity, out Release1SmallCourtesyRewardIdentity identity) || (object)identity == null || !string.Equals(release1NativeEffectJournalEntry.EffectKind, "Reward", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.SourceIdentity, "oc-wrong-address", StringComparison.Ordinal) || !string.Equals(release1NativeEffectJournalEntry.DestinationIdentity, "player-cash", StringComparison.Ordinal)) { return BlockReward(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.ExecutionBlocked) { return Release1WrongAddressDeliveryStatus.Ambiguous; } if (!TryReadCashBalance(out var balance)) { return Release1WrongAddressDeliveryStatus.Rejected; } bool flag = WithinCashTolerance(balance, identity.BaselineCash, identity.VerificationTolerance); bool flag2 = WithinCashTolerance(balance, identity.ExpectedCash, identity.VerificationTolerance); if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) { if (flag2) { return MarkRewardApplied(release1NativeEffectJournalEntry); } if (!flag) { return BlockReward(release1NativeEffectJournalEntry); } Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus; try { release1SmallCourtesyWorldMutationStatus = _world.TryChangeCashBalance(identity.WholeDollarAmount); } catch { return BlockReward(release1NativeEffectJournalEntry); } if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { return BlockReward(release1NativeEffectJournalEntry); } if (!TryReadCashBalance(out var balance2) || !WithinCashTolerance(balance2, identity.ExpectedCash, identity.VerificationTolerance)) { return BlockReward(release1NativeEffectJournalEntry); } return MarkRewardApplied(release1NativeEffectJournalEntry); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied) { if (!flag2) { return Release1WrongAddressDeliveryStatus.Ambiguous; } if (capturedRevision > _story.LastPersistedRevision) { return Release1WrongAddressDeliveryStatus.AwaitingAppliedSave; } string authorizedStoryCorrelationId = release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId; if (string.IsNullOrEmpty(authorizedStoryCorrelationId)) { return Release1WrongAddressDeliveryStatus.Ambiguous; } if (!_story.TryCommitNativeEffect(release1NativeEffectJournalEntry.EffectId, authorizedStoryCorrelationId, capturedRevision).Accepted) { return Release1WrongAddressDeliveryStatus.Rejected; } return Release1WrongAddressDeliveryStatus.Committed; } return Release1WrongAddressDeliveryStatus.Committed; } private Release1WrongAddressDeliveryStatus MarkRewardApplied(Release1NativeEffectJournalEntry effect) { if (!_story.TryMarkNativeEffectApplied(effect.EffectId, $"wrong-address-reward-native-v1-a{effect.Attempt}", Release1NativeEffectPersistenceMode.RevertTolerant).Accepted) { return Release1WrongAddressDeliveryStatus.Rejected; } return Release1WrongAddressDeliveryStatus.Paid; } private Release1WrongAddressDeliveryStatus BlockConsumption(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } ReleaseSlotLock(); return Release1WrongAddressDeliveryStatus.Ambiguous; } private Release1WrongAddressDeliveryStatus BlockReward(Release1NativeEffectJournalEntry effect) { if (effect.Phase == Release1NativeEffectPhase.Prepared && !effect.ExecutionBlocked) { _story.TryRecordNativeEffectIssuance(effect.EffectId, Release1NativeEffectIssuanceOutcome.Ambiguous); } return Release1WrongAddressDeliveryStatus.Ambiguous; } private bool TryReadCashBalance(out float balance) { balance = 0f; try { return _world.TryReadCashBalance(out balance) == Release1SmallCourtesyWorldReadStatus.Ready && float.IsFinite(balance) && balance >= 0f; } catch { return false; } } private bool TryAcquireSlotLock(string deadDropGuid, int slotIndex) { if (_lockedDropGuid != null) { if (string.Equals(_lockedDropGuid, deadDropGuid, StringComparison.Ordinal)) { return _lockedSlotIndex == slotIndex; } return false; } _lockedDropGuid = deadDropGuid; _lockedSlotIndex = slotIndex; try { Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = _world.TrySetSlotLocked(deadDropGuid, slotIndex, locked: true); if (release1SmallCourtesyWorldMutationStatus != Release1SmallCourtesyWorldMutationStatus.Succeeded) { if (release1SmallCourtesyWorldMutationStatus == Release1SmallCourtesyWorldMutationStatus.Ambiguous) { ReleaseSlotLock(); } else { _lockedDropGuid = null; _lockedSlotIndex = -1; } return false; } return true; } catch { ReleaseSlotLock(); return false; } } private void ReleaseSlotLock() { if (_lockedDropGuid == null || _lockedSlotIndex < 0) { return; } try { if (_world.TrySetSlotLocked(_lockedDropGuid, _lockedSlotIndex, locked: false) == Release1SmallCourtesyWorldMutationStatus.Succeeded) { _lockedDropGuid = null; _lockedSlotIndex = -1; } } catch { } } private static bool WithinCashTolerance(float actual, float expected, float tolerance) { if (float.IsFinite(actual)) { return MathF.Abs(actual - expected) <= tolerance; } return false; } private static bool MatchesCargoSlot(Release1SmallCourtesySlotSnapshot slot, Release1SmallCourtesyCargoIdentity identity, int expectedQuantity) { if (slot.SlotIndex != identity.SlotIndex || slot.Quantity != expectedQuantity) { return false; } if (expectedQuantity == 0) { return true; } if (slot.IsPackaged && string.Equals(slot.ProductId, identity.ProductId, StringComparison.Ordinal) && string.Equals(slot.PackagingId, identity.PackageId, StringComparison.Ordinal)) { return slot.MonetaryValue == identity.DepositedMonetaryValue; } return false; } private bool TryReadSlot(string deadDropGuid, int slotIndex, out Release1SmallCourtesySlotSnapshot slot) { slot = null; if (!TryReadSlots(deadDropGuid, out IReadOnlyList slots)) { return false; } slot = slots.SingleOrDefault((Release1SmallCourtesySlotSnapshot candidate) => candidate.SlotIndex == slotIndex); return (object)slot != null; } private static string RewardEffectId(int attempt) { return $"wrong-address-reward-v1-a{attempt}"; } private void RefreshDropBinding() { if (!_loadActive || _disposed || !TryGetActiveStage(out Release1MissionRecord _, out Release1WrongAddressAssignment assignment, out string _)) { DisposeDropSubscription(); return; } Release1WrongAddressProgress? release1WrongAddressProgress = Progress(assignment.Attempt); string text = (((object)release1WrongAddressProgress != null && release1WrongAddressProgress.Custody) ? assignment.HandoffDropGuid : assignment.SourceDropGuid); if (_dropSubscription != null && string.Equals(_boundDropGuid, text, StringComparison.Ordinal)) { return; } DisposeDropSubscription(); try { if (_world.TrySubscribeDeadDropClosed(text, OnDropClosed, out IRelease1SmallCourtesyDropSubscription subscription) != Release1SmallCourtesyWorldReadStatus.Ready || subscription == null || !string.Equals(subscription.DeadDropGuid, text, StringComparison.Ordinal)) { subscription?.Dispose(); return; } _dropSubscription = subscription; _boundDropGuid = text; } catch { DisposeDropSubscription(); } } private void OnDropClosed(string deadDropGuid) { TryHandleDropClosed(deadDropGuid); } private void DisposeDropSubscription() { try { _dropSubscription?.Dispose(); } catch { } _dropSubscription = null; _boundDropGuid = null; } internal static Release1WrongAddressAssignmentMode? ExpectedAssignmentMode(Release1MissionState missionState) { switch (missionState) { case Release1MissionState.Accepted: case Release1MissionState.Active: return Release1WrongAddressAssignmentMode.Primary; case Release1MissionState.MakeGoodOffered: case Release1MissionState.MakeGoodActive: return Release1WrongAddressAssignmentMode.MakeGood; case Release1MissionState.RecoveryAvailable: case Release1MissionState.RecoveryActive: return Release1WrongAddressAssignmentMode.Recovery; default: return null; } } private bool TryGetActiveStage(out Release1MissionRecord mission, out Release1WrongAddressAssignment assignment, out string authorization) { mission = null; assignment = null; authorization = string.Empty; Release1StoryState state = _story.State; if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return false; } mission = Mission(state); Release1WrongAddressAssignmentMode? expectedMode = ExpectedAssignmentMode(mission.State); if (!expectedMode.HasValue) { return false; } int missionAttempt = mission.Attempt; assignment = state.WrongAddressAssignments.SingleOrDefault((Release1WrongAddressAssignment candidate) => candidate.Attempt == missionAttempt && candidate.Mode == expectedMode.Value); if ((object)assignment == null) { return false; } authorization = ((expectedMode == Release1WrongAddressAssignmentMode.Primary) ? (mission.AcceptedLogicalCorrelations.LastOrDefault((string value) => Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) && correlation.TransitionKind == Release1TransitionKind.MissionActivated) ?? string.Empty) : assignment.AuthorizationCorrelationId); return !string.IsNullOrEmpty(authorization); } private Release1WrongAddressProgress? Progress(int attempt) { return _story.State?.WrongAddressProgress.SingleOrDefault((Release1WrongAddressProgress progress) => progress.Attempt == attempt); } private bool HasConsumptionEffect(int attempt) { return _story.State?.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.EffectId == ConsumptionEffectId(attempt)) ?? false; } private bool TryReadSlots(string deadDropGuid, out IReadOnlyList slots) { slots = Array.Empty(); try { if (_world.TryReadDeadDropSlots(deadDropGuid, out IReadOnlyList slots2) != Release1SmallCourtesyWorldReadStatus.Ready || slots2 == null) { return false; } Release1SmallCourtesySlotSnapshot[] array = slots2.ToArray(); Release1SmallCourtesySlotSnapshot[] array2 = array; for (int i = 0; i < array2.Length; i++) { array2[i].Validate(); } if (array.Select((Release1SmallCourtesySlotSnapshot slot) => slot.SlotIndex).Distinct().Count() != array.Length) { return false; } slots = array; return true; } catch { return false; } } private void ReportHoldOnce(int attempt, string message) { if (_reportedHolds.Add($"a{attempt}:{message}")) { _log($"Wrong Address attempt {attempt} is holding: {message}"); } } private static Release1ConsignmentFingerprint Fingerprint(Release1WrongAddressAssignment assignment) { return new Release1ConsignmentFingerprint(assignment.ProductId, assignment.PackagingId, assignment.PackageQuantity); } private static string ConsumptionEffectId(int attempt) { return $"wrong-address-cargo-v1-a{attempt}"; } } public enum Release1WrongAddressCardStage { Hidden, Offer, Review, InsufficientDrops, PersistenceDeferred, DecisionRejected, AwaitingActivation, Active, InCustody, Ambiguous, Completed } public sealed record Release1WrongAddressViewModel(bool Visible, Release1WrongAddressCardStage Stage, string Title, string Body, bool CanReview, bool CanAccept, bool CanDefer); public static class Release1WrongAddressPresentation { public const string Title = "Wrong Address"; public const string OfferText = "Some fucking moron delivered to the wrong address. They have been taken care of, of course. I need you to fix his fuckup."; public const string RefusalText = "I need two clear drops before I can point you at one. Try me again when the town has room."; public const string QuestionText = "Do you have the balls?"; public const string PaymentText = "We pay 125 percent of the package value the moment you hand it over."; public static Release1WrongAddressViewModel Build(Release1StoryState? state, Release1WrongAddressOfferStatus offerStatus, Release1WrongAddressQuote? reviewedQuote, Release1WrongAddressProgress? progress, string? feedback = null, Release1WrongAddressCardStage? feedbackStage = null) { if (!string.IsNullOrWhiteSpace(feedback)) { return Visible(feedbackStage ?? Release1WrongAddressCardStage.PersistenceDeferred, Copy(feedback)); } if (offerStatus == Release1WrongAddressOfferStatus.InsufficientEmptyDeadDrops) { return new Release1WrongAddressViewModel(Visible: true, Release1WrongAddressCardStage.InsufficientDrops, "Wrong Address", Copy("I need two clear drops before I can point you at one. Try me again when the town has room."), CanReview: true, CanAccept: false, CanDefer: false); } if ((object)state == null || state.RelationshipState != Release1RelationshipState.Accepted) { return Hidden(); } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; Release1WrongAddressAssignmentMode? expectedMode = Release1WrongAddressMissionService.ExpectedAssignmentMode(mission.State); Release1WrongAddressAssignment release1WrongAddressAssignment = state.WrongAddressAssignments.SingleOrDefault((Release1WrongAddressAssignment candidate) => candidate.Attempt == mission.Attempt && (!expectedMode.HasValue || candidate.Mode == expectedMode.Value)); if ((object)release1WrongAddressAssignment == null) { if ((object)reviewedQuote != null) { return Terms(reviewedQuote.Assignment, reviewedQuote.DeadlineDurationHours); } if (offerStatus == Release1WrongAddressOfferStatus.Available) { return new Release1WrongAddressViewModel(Visible: true, Release1WrongAddressCardStage.Offer, "Wrong Address", Copy("Some fucking moron delivered to the wrong address. They have been taken care of, of course. I need you to fix his fuckup."), CanReview: true, CanAccept: false, CanDefer: false); } return Hidden(); } if (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.MissionKey == "release1.wrong-address" && effect.Attempt == mission.Attempt && effect.ExecutionBlocked)) { return Active(release1WrongAddressAssignment, mission, Release1WrongAddressCardStage.Ambiguous, "Stopped on conflicting evidence. It will not retry."); } if (mission.State == Release1MissionState.Satisfied) { return Active(release1WrongAddressAssignment, mission, Release1WrongAddressCardStage.Completed, "You found a way to not fuck it up. Delivered and paid."); } if (mission.State == Release1MissionState.Accepted) { return Active(release1WrongAddressAssignment, mission, Release1WrongAddressCardStage.AwaitingActivation, "You twat. Collect the package from the wrong address."); } if ((object)progress == null || !progress.Custody) { return Active(release1WrongAddressAssignment, mission, Release1WrongAddressCardStage.Active, "You twat. Collect the package from the wrong address."); } return Active(release1WrongAddressAssignment, mission, Release1WrongAddressCardStage.InCustody, "Finish the fucking job and take it to the right address."); } private static Release1WrongAddressViewModel Terms(Release1WrongAddressAssignment assignment, double? deadlineHours) { return new Release1WrongAddressViewModel(Visible: true, Release1WrongAddressCardStage.Review, "Wrong Address", Copy(Release1PlayerCopy.JoinLines(Release1PlayerCopy.AttemptHeader(assignment.Mode, assignment.Attempt), "1 " + assignment.PackagingName + " of " + assignment.ProductName, $"Pick up: {assignment.SourceDropName}. Drop off: {assignment.HandoffDropName}.", "Deadline: " + Release1PlayerCopy.Deadline(deadlineHours), "We pay 125 percent of the package value the moment you hand it over.", "Do you have the balls?")), CanReview: false, CanAccept: true, CanDefer: true); } private static Release1WrongAddressViewModel Active(Release1WrongAddressAssignment assignment, Release1MissionRecord mission, Release1WrongAddressCardStage stage, string status) { return new Release1WrongAddressViewModel(Visible: true, stage, "Wrong Address", Copy(string.Join("\n", "Status: " + status, "1 " + assignment.PackagingName + " of " + assignment.ProductName, "Pick up: " + assignment.SourceDropName, "Drop off: " + assignment.HandoffDropName, "Deadline: " + Release1PlayerCopy.Deadline(DeadlineDuration(mission)), "We pay 125 percent of the package value the moment you hand it over.")), CanReview: false, CanAccept: false, CanDefer: false); } private static double? DeadlineDuration(Release1MissionRecord mission) { if (mission.DeadlineGameTimeHours.HasValue && mission.AcceptedGameTimeHours.HasValue) { return mission.DeadlineGameTimeHours.Value - mission.AcceptedGameTimeHours.Value; } return null; } private static Release1WrongAddressViewModel Visible(Release1WrongAddressCardStage stage, string body) { return new Release1WrongAddressViewModel(Visible: true, stage, "Wrong Address", body, CanReview: false, CanAccept: false, CanDefer: false); } private static Release1WrongAddressViewModel Hidden() { return new Release1WrongAddressViewModel(Visible: false, Release1WrongAddressCardStage.Hidden, "Wrong Address", string.Empty, CanReview: false, CanAccept: false, CanDefer: false); } private static string Copy(string value) { return Release1PlayerCopy.Normalize(value); } } public sealed class Release1WrongAddressPresenter : IDisposable { private readonly Release1WrongAddressMissionService _service; private readonly Release1StoryRuntimeService _story; private readonly Action? _log; private string? _feedback; private Release1WrongAddressCardStage? _feedbackStage; private bool _disposed; public Release1WrongAddressViewModel View { get { if (!_disposed) { return Release1WrongAddressPresentation.Build(_story.State, _service.OfferStatus, _service.ReviewedQuote, CurrentProgress(), _feedback, _feedbackStage); } return Release1WrongAddressPresentation.Build(null, Release1WrongAddressOfferStatus.Disposed, null, null); } } public Release1WrongAddressPresenter(Release1WrongAddressMissionService service, Release1StoryRuntimeService story, Action? log = null) { _service = service ?? throw new ArgumentNullException("service"); _story = story ?? throw new ArgumentNullException("story"); _log = log; } public Release1WrongAddressReviewResult TryReview() { Release1WrongAddressReviewResult release1WrongAddressReviewResult = _service.TryReview(); _feedback = ((release1WrongAddressReviewResult.Status == Release1WrongAddressReviewStatus.Ready) ? null : release1WrongAddressReviewResult.Message); _feedbackStage = ((_feedback == null) ? ((Release1WrongAddressCardStage?)null) : new Release1WrongAddressCardStage?((release1WrongAddressReviewResult.Status == Release1WrongAddressReviewStatus.InsufficientEmptyDeadDrops) ? Release1WrongAddressCardStage.InsufficientDrops : Release1WrongAddressCardStage.DecisionRejected)); if (_feedback != null) { _log?.Invoke($"Wrong Address review failed: {release1WrongAddressReviewResult.Status}, {release1WrongAddressReviewResult.Message}"); } return release1WrongAddressReviewResult; } public Release1WrongAddressDecisionResult TryAccept() { return Handle(_service.TryAccept()); } public Release1WrongAddressDecisionResult TryDefer() { return Handle(_service.TryDefer()); } public void ClearFeedback() { _feedback = null; _feedbackStage = null; } public void Dispose() { _feedback = null; _feedbackStage = null; _disposed = true; } private Release1WrongAddressProgress? CurrentProgress() { Release1StoryState state = _story.State; if ((object)state == null) { return null; } Release1MissionRecord mission = state.Missions[Release1MissionCatalog.IndexOf("release1.wrong-address")]; return state.WrongAddressProgress.SingleOrDefault((Release1WrongAddressProgress progress) => progress.Attempt == mission.Attempt); } private Release1WrongAddressDecisionResult Handle(Release1WrongAddressDecisionResult result) { Release1WrongAddressDecisionStatus status = result.Status; if ((uint)status <= 2u) { _feedback = null; _feedbackStage = null; } else if (result.Status == Release1WrongAddressDecisionStatus.InsufficientEmptyDeadDrops) { _feedback = null; _feedbackStage = null; } else { _feedback = result.Message; _feedbackStage = ((result.Status == Release1WrongAddressDecisionStatus.PersistenceDeferred) ? Release1WrongAddressCardStage.PersistenceDeferred : Release1WrongAddressCardStage.DecisionRejected); _log?.Invoke($"Wrong Address decision failed: {result.Status}, {result.Message}"); } return result; } } public sealed class Release1WrongAddressQuest : Quest { public const string DisplayTitle = "Wrong Address"; private readonly QuestEntry[] _entries; protected override string Title => Release1PlayerCopy.Normalize("Wrong Address"); protected override string Description => Release1PlayerCopy.Normalize("Wrong Address"); public Release1WrongAddressQuest() { string text = Release1PlayerCopy.Normalize("Wrong Address"); _entries = (QuestEntry[])(object)new QuestEntry[2] { ((Quest)this).AddEntry(text, (Vector3?)null), ((Quest)this).AddEntry(text, (Vector3?)null) }; } public void Project(Release1DesiredQuest quest) { ArgumentNullException.ThrowIfNull(quest, "quest"); if (quest.Entries.Count != _entries.Length) { throw new InvalidOperationException($"Release 1 Wrong Address quest desired entry count ({quest.Entries.Count}) does not match the quest's native entry count ({_entries.Length})."); } for (int i = 0; i < _entries.Length; i++) { ApplyEntry(_entries[i], quest.Entries[i]); } } public IReadOnlyList ReadEntryStates() { return _entries.Select((QuestEntry entry) => MapState(entry.State)).ToArray(); } private static void ApplyEntry(QuestEntry entry, Release1DesiredEntry desired) { //IL_0035: 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) entry.Title = Release1PlayerCopy.Normalize(desired.Text); Release1WorldPoint marker = desired.Marker; entry.POIPosition = (Vector3)(((object)marker != null) ? new Vector3(marker.X, marker.Y, marker.Z) : Vector3.zero); switch (Release1NativePresentationSupport.ClassifyEntryAction(desired.State)) { case Release1QuestEntryProjectionAction.Begin: entry.Begin(); break; case Release1QuestEntryProjectionAction.Complete: entry.Complete(); break; default: entry.SetState((QuestState)0); break; } } private static Release1DesiredEntryState MapState(QuestState state) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0002: Invalid comparison between Unknown and I4 //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Invalid comparison between Unknown and I4 if ((int)state != 1) { if ((int)state == 2) { return Release1DesiredEntryState.Complete; } return Release1DesiredEntryState.Inactive; } return Release1DesiredEntryState.Active; } } public enum Release1StagingHarnessStatus { Succeeded, NoDiscoveredProduct, NoEmptyDeadDrop, Unavailable, Rejected, Faulted, Ambiguous, NeedsOptionB } public sealed record Release1StagingHarnessResult(Release1StagingHarnessStatus Status, IReadOnlyList Lines); public static class Release1WrongAddressStagingHarness { public static Release1StagingHarnessResult TryStage(IRelease1SmallCourtesyWorld world) { if (world == null) { return Faulted("the world was not available."); } List list = new List(); try { IReadOnlyList products; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadProducts(out products); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"products could not be read, status {release1SmallCourtesyWorldReadStatus}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), list); } Release1SmallCourtesyProductCandidate release1SmallCourtesyProductCandidate = (from candidate in products ?? Array.Empty() where candidate?.IsDiscovered ?? false orderby candidate.AskingPrice descending select candidate).ThenBy((Release1SmallCourtesyProductCandidate candidate) => candidate.ProductId, StringComparer.Ordinal).FirstOrDefault(); if ((object)release1SmallCourtesyProductCandidate == null) { list.Add("no discovered product was available to stage."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.NoDiscoveredProduct, list); } list.Add($"selected product {release1SmallCourtesyProductCandidate.ProductId} at asking price {release1SmallCourtesyProductCandidate.AskingPrice}."); Release1SmallCourtesyPackagingCandidate packaging; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = world.TryReadPackaging(Release1SmallCourtesyPackageKind.Brick, out packaging); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"brick packaging could not be read, status {release1SmallCourtesyWorldReadStatus2}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus2), list); } list.Add("selected packaging " + packaging.PackagingId + "."); IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus3 = world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus3 != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"dead drops could not be read, status {release1SmallCourtesyWorldReadStatus3}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus3), list); } Release1SmallCourtesyDropCandidate release1SmallCourtesyDropCandidate = (drops ?? Array.Empty()).Where((Release1SmallCourtesyDropCandidate candidate) => candidate?.IsEmpty ?? false).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal).FirstOrDefault(); if ((object)release1SmallCourtesyDropCandidate == null) { list.Add("no empty dead drop was available to stage into."); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.NoEmptyDeadDrop, list); } list.Add($"selected dead drop {release1SmallCourtesyDropCandidate.Name} with GUID {release1SmallCourtesyDropCandidate.Guid}."); string reason; Release1SmallCourtesyWorldMutationStatus release1SmallCourtesyWorldMutationStatus = world.TryInsertPackagedProduct(release1SmallCourtesyDropCandidate.Guid, 0, release1SmallCourtesyProductCandidate.ProductId, packaging.PackagingId, 1, out reason); list.Add($"insert result {release1SmallCourtesyWorldMutationStatus}, reason: {reason}"); return new Release1StagingHarnessResult(MapMutationStatus(release1SmallCourtesyWorldMutationStatus), list); } catch (Exception ex) { list.Add("an exception was thrown while staging: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } public static Release1StagingHarnessResult TryDump(IRelease1SmallCourtesyWorld world) { if (world == null) { return Faulted("the world was not available."); } List list = new List(); try { IReadOnlyList drops; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = world.TryReadDeadDrops(out drops); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"dead drops could not be read, status {release1SmallCourtesyWorldReadStatus}."); return new Release1StagingHarnessResult(MapReadStatus(release1SmallCourtesyWorldReadStatus), list); } List list2 = (drops ?? Array.Empty()).Where((Release1SmallCourtesyDropCandidate candidate) => (object)candidate != null).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal).ToList(); list.Add($"dead drops read: {list2.Count}"); foreach (Release1SmallCourtesyDropCandidate item in list2) { IReadOnlyList slots; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus2 = world.TryReadDeadDropSlots(item.Guid, out slots); if (release1SmallCourtesyWorldReadStatus2 != Release1SmallCourtesyWorldReadStatus.Ready) { list.Add($"drop {item.Name} ({item.Guid}) slots {release1SmallCourtesyWorldReadStatus2}"); continue; } List list3 = (from candidate in slots ?? Array.Empty() where (object)candidate != null orderby candidate.SlotIndex select candidate).ToList(); int count = list3.Count; int value = list3.Count((Release1SmallCourtesySlotSnapshot s) => s.Quantity > 0); list.Add($"drop {item.Name} ({item.Guid}) slots {count}, occupied {value}"); foreach (Release1SmallCourtesySlotSnapshot item2 in list3.Where((Release1SmallCourtesySlotSnapshot candidate) => candidate.Quantity > 0)) { list.Add($"drop {item.Name} ({item.Guid}) slot {item2.SlotIndex} product {item2.ProductId} packaging {item2.PackagingId} quantity {item2.Quantity} value {item2.MonetaryValue}"); } } return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Succeeded, list); } catch (Exception ex) { list.Add("an exception was thrown while dumping: " + ex.Message); return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, list); } } private static Release1StagingHarnessResult Faulted(string message) { return new Release1StagingHarnessResult(Release1StagingHarnessStatus.Faulted, new string[1] { message }); } private static Release1StagingHarnessStatus MapReadStatus(Release1SmallCourtesyWorldReadStatus status) { return status switch { Release1SmallCourtesyWorldReadStatus.NotAuthoritative => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldReadStatus.Pending => Release1StagingHarnessStatus.Unavailable, Release1SmallCourtesyWorldReadStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } private static Release1StagingHarnessStatus MapMutationStatus(Release1SmallCourtesyWorldMutationStatus status) { return status switch { Release1SmallCourtesyWorldMutationStatus.Succeeded => Release1StagingHarnessStatus.Succeeded, Release1SmallCourtesyWorldMutationStatus.Rejected => Release1StagingHarnessStatus.Rejected, Release1SmallCourtesyWorldMutationStatus.Unavailable => Release1StagingHarnessStatus.Unavailable, _ => Release1StagingHarnessStatus.Faulted, }; } } public sealed class RuntimePropertyHost : IRuntimePropertyHost { private sealed class RuntimePropertyNavigationActions : IFishWarehouseContinuousNavigationActions { private readonly RuntimePropertyHost _host; public RuntimePropertyNavigationActions(RuntimePropertyHost host) { _host = host; } public bool TryPreflightGeometry() { if (!IsAlive((Object?)(object)_host._anchor) || !IsAlive((Object?)(object)_host._property)) { return false; } RuntimePropertyHost host = _host; if (host._garageOpening == null) { host._garageOpening = new FishWarehouseGarageOpeningHost(); } if (!_host._garageOpening.TryPreflight(_host._anchor, ((Component)_host._property).transform, out FishWarehouseGarageOpeningPreflightResult result, Log) || (object)result == null) { return false; } return true; } public bool TryActivateRoom() { FishWarehouseGarageOpeningProjection fishWarehouseGarageOpeningProjection = _host._garageOpening?.Preflight?.Projection; if (!IsAlive((Object?)(object)_host._anchor) || !IsAlive((Object?)(object)_host._property) || (object)fishWarehouseGarageOpeningProjection == null) { return false; } RuntimePropertyHost host = _host; if (host._interiorRoom == null) { host._interiorRoom = new FishWarehouseInteriorRoomHost(); } if (_host._interiorRoom.TryPrepare(_host._anchor, ((Component)_host._property).transform, fishWarehouseGarageOpeningProjection, Log)) { return _host._interiorRoom.Activate(); } return false; } public bool TryOpenGarage() { return _host._garageOpening?.TryOpen(Log) ?? false; } public string GetBuildableAndConfigurableSignature() { try { Property? property = _host._property; int valueOrDefault = ((property == null) ? ((int?)null) : property.Configurables?.Count).GetValueOrDefault(); Property? property2 = _host._property; int valueOrDefault2 = ((property2 == null) ? ((int?)null) : property2.Employees?.Count).GetValueOrDefault(); return $"configurables={valueOrDefault};employees={valueOrDefault2}"; } catch { return "unavailable"; } } public FishWarehouseNativeNavigationBuildResult TryBuildNavigation() { FishWarehouseGarageOpeningProjection fishWarehouseGarageOpeningProjection = _host._garageOpening?.Preflight?.Projection; FishWarehouseBuildSurfaceHost? buildSurface = _host._buildSurface; object obj; if (buildSurface == null) { obj = null; } else { EmployeeHome? currentEmployeeHome = buildSurface.CurrentEmployeeHome; obj = ((currentEmployeeHome != null) ? ((Component)currentEmployeeHome).transform : null); } Transform restoredLocker = (Transform)obj; PackagingStation representativePackagingStation = _host.FindRepresentativePackagingStation(); Property? property = _host._property; Transform[] array = Il2CppArrayBase.op_Implicit((Il2CppArrayBase)(object)((property != null) ? property.EmployeeIdlePoints : null)); if (!IsAlive((Object?)(object)_host._anchor)) { LogWarning("Fish Warehouse employee navigation build rejected: anchor was unavailable."); return FishWarehouseNativeNavigationBuildResult.Failed; } if (!IsAlive((Object?)(object)_host._property)) { LogWarning("Fish Warehouse employee navigation build rejected: property root was unavailable."); return FishWarehouseNativeNavigationBuildResult.Failed; } if ((object)fishWarehouseGarageOpeningProjection == null) { LogWarning("Fish Warehouse employee navigation build rejected: garage projection was unavailable."); return FishWarehouseNativeNavigationBuildResult.Failed; } int num = 10; if (array == null) { LogWarning($"Fish Warehouse employee navigation build rejected missing-idle-points: expected={num} actual=0."); return FishWarehouseNativeNavigationBuildResult.Failed; } if (array.Length != num) { string value = ((array.Length < num) ? "too-few-idle-points" : "too-many-idle-points"); LogWarning($"Fish Warehouse employee navigation build rejected {value}: expected={num} actual={array.Length}."); return FishWarehouseNativeNavigationBuildResult.Failed; } if (array.Any((Transform point) => !IsAlive((Object?)(object)point))) { LogWarning($"Fish Warehouse employee navigation build rejected malformed-idle-points: expected={num} valid live points."); return FishWarehouseNativeNavigationBuildResult.Failed; } RuntimePropertyHost host = _host; if (host._navigation == null) { host._navigation = new FishWarehouseNativeNavigationHost(); } return _host._navigation.TryBuild(_host._anchor, ((Component)_host._property).transform, fishWarehouseGarageOpeningProjection, array, restoredLocker, representativePackagingStation, _host._persistenceEmployeeAgentTypeId, Log); } public bool ValidateNavigation() { return _host._navigation?.IsBuilt ?? false; } public bool RemoveNavigation() { try { _host._garageCrossing?.Deactivate(); _host._garageCrossing = null; _host._dockDeliveryBridge = null; return _host._navigation?.Remove() ?? true; } catch (Exception ex) { LogWarning("Fish Warehouse employee navigation removal was partial: " + ex.Message); return false; } } public void RestoreGarage() { try { _host._garageOpening?.Restore(Log); } catch (Exception ex) { LogWarning("Fish Warehouse garage restoration was partial: " + ex.Message); } } public void DisposeRoom() { try { _host._interiorRoom?.Dispose(); } catch (Exception ex) { LogWarning("Fish Warehouse authored interior room cleanup was partial: " + ex.Message); } finally { _host._interiorRoom = null; } } } private sealed record DocksIdentity(int ObjectId, ulong SceneId, string State) { public static DocksIdentity Empty => new DocksIdentity(0, 0uL, ""); } private const string DocksWarehouseCode = "dockswarehouse"; private const string IdentityJson = "{\"propertyCode\":\"oc_fishwarehouse\",\"propertyName\":\"Syndicate Warehouse\"}"; private GameObject? _root; private NetworkObject? _networkObject; private Property? _property; private PrefabObjects? _bucket; private NetworkManager? _networkManager; private ServerManager? _serverManager; private object? _authoredCollection; private Transform? _anchor; private FishWarehouseBuildSurfaceHost? _buildSurface; private FishWarehouseInteriorRoomHost? _interiorRoom; private FishWarehouseGarageOpeningHost? _garageOpening; private FishWarehouseNativeNavigationHost? _navigation; private FishWarehouseGarageCrossingHost? _garageCrossing; private FishWarehouseDockDeliveryBridgeHost? _dockDeliveryBridge; private FishWarehouseContinuousNavigationCoordinator? _continuousNavigationCoordinator; private FishWarehouseContinuousNavigationState _employeeNavigationState; private FishWarehousePropertyBoundsHost? _propertyBounds; private FishWarehouseEmployeeInfrastructureHost? _employeeInfrastructure; private FishWarehouseFrontageClearanceHost? _frontageClearance; private bool _frontageClearanceCleanupPending; private FishWarehouseLoadingDockHost? _loadingDocks; private bool _managementDiagnosticFailureReported; private RectTransform? _managementWorldspaceUiContainer; private DocksIdentity _docksBefore = DocksIdentity.Empty; private RuntimePropertyDefinition? _definition; private bool _persistenceObjectReplayRequired; private bool _persistenceObjectsReplayed; private int? _persistenceEmployeeAgentTypeId; public Property? CurrentProperty => _property; public Transform? CurrentAnchor => _anchor; public NetworkObject? CurrentNetworkObject => _networkObject; public GameObject? CurrentRoot => _root; public bool CanRestoreDeliveries { get { if (_property != null && Read(() => _property.IsOwned)) { return _loadingDocks?.IsReady ?? false; } return false; } } public FishWarehouseContinuousNavigationState EmployeeNavigationState { get { if (_employeeNavigationState != FishWarehouseContinuousNavigationState.Failed) { return _continuousNavigationCoordinator?.State ?? _employeeNavigationState; } return FishWarehouseContinuousNavigationState.Failed; } } public bool EmployeeNavigationReady => EmployeeNavigationState == FishWarehouseContinuousNavigationState.Ready; public int? EmployeeNavigationAgentTypeId { get { if (!EmployeeNavigationReady) { return null; } return _navigation?.ResolvedEmployeeAgentTypeId; } } public bool PersistenceRuntimeReady { get { if (IsAlive((Object?)(object)_property) && Read(() => _property.IsOwned)) { FishWarehouseBuildSurfaceHost? buildSurface = _buildSurface; if (buildSurface != null && buildSurface.IsReady) { return RuntimePropertyDefinition.FishWarehouse.HasBuildGridGuid(_buildSurface.GridGuid); } } return false; } } public RuntimePropertyHostResult Start(RuntimePropertyDefinition definition) { //IL_0212: Unknown result type (might be due to invalid IL or missing references) //IL_021c: Expected O, but got Unknown //IL_022e: Unknown result type (might be due to invalid IL or missing references) //IL_023a: Unknown result type (might be due to invalid IL or missing references) //IL_05f7: Unknown result type (might be due to invalid IL or missing references) if (_root != null) { return RuntimePropertyHostResult.Failure("duplicate-start", "Runtime Property host already owns a root."); } if (_frontageClearanceCleanupPending) { if (_frontageClearance == null || !_frontageClearance.Dispose(Log)) { return RuntimePropertyHostResult.Failure("pending-frontage-cleanup", "Previous Fish Warehouse frontage cleanup is still partial."); } _frontageClearance = null; _frontageClearanceCleanupPending = false; } _definition = definition; bool flag = false; bool flag2 = false; bool flag3 = false; string stage = "preconditions"; try { GameObject val = FindAnchor(definition.AnchorPath); if (val == null) { return RuntimePropertyHostResult.Failure(stage, "Physical anchor was not found: " + definition.AnchorPath); } _anchor = val.transform; Property val2 = FindProperty("dockswarehouse"); NetworkObject val3 = ((val2 == null) ? null : ResolveNetworkObject(val2)); _networkManager = ((val3 != null) ? val3.NetworkManager : null); _serverManager = InstanceFinder.ServerManager; NetworkManager? networkManager = _networkManager; _authoredCollection = ((networkManager != null) ? networkManager.SpawnablePrefabs : null); if (val2 == null || val3 == null || _networkManager == null) { return RuntimePropertyHostResult.Failure(stage, "Docks Warehouse or its NetworkManager was not found."); } if (_authoredCollection == null) { return RuntimePropertyHostResult.Failure(stage, "Authored FishNet prefab collection was unavailable."); } if (_serverManager == null || !Read(() => _serverManager.OneServerStarted())) { return RuntimePropertyHostResult.Failure(stage, "A started FishNet server is required; client-only lifecycle is not enabled in this slice."); } if (FindProperty(definition.PropertyCode) != null) { return RuntimePropertyHostResult.Failure(stage, "Property code " + definition.PropertyCode + " is already registered."); } if (_networkManager.GetPrefabObjects(definition.RuntimeCollectionId, false) != null) { return RuntimePropertyHostResult.Failure(stage, $"Runtime prefab collection {definition.RuntimeCollectionId} already exists."); } _docksBefore = CaptureDocksIdentity(val3); _root = new GameObject(definition.RootName); _root.transform.SetPositionAndRotation(val.transform.position, val.transform.rotation); _root.SetActive(false); _networkObject = _root.AddComponent(); _property = _root.AddComponent(); stage = "identity"; JsonUtility.FromJsonOverwrite("{\"propertyCode\":\"oc_fishwarehouse\",\"propertyName\":\"Syndicate Warehouse\"}", (Object)(object)_property); if (!string.Equals(_property.PropertyCode, definition.PropertyCode, StringComparison.Ordinal) || !string.Equals(_property.PropertyName, definition.DisplayName, StringComparison.Ordinal)) { return FailAndCleanup(stage, "Serialized Property identity did not apply.", flag, flag2, flag3); } stage = "property-bounds"; _propertyBounds = new FishWarehousePropertyBoundsHost(); if (!_propertyBounds.TryAttach(_property, Log)) { return FailAndCleanup(stage, "Authored Property bounds could not be attached.", flag, flag2, flag3); } stage = "employee-infrastructure"; _employeeInfrastructure = new FishWarehouseEmployeeInfrastructureHost(); if (!_employeeInfrastructure.TryAttach(_property, Log)) { return FailAndCleanup(stage, "Employee infrastructure could not be attached.", flag, flag2, flag3); } stage = "loading-docks"; _frontageClearance = new FishWarehouseFrontageClearanceHost(); if (!_frontageClearance.TryAttach(_property, Log)) { _frontageClearanceCleanupPending = !_frontageClearance.Dispose(Log); if (_frontageClearanceCleanupPending) { return FailAndCleanup(stage, "Frontage clearance cleanup remained partial.", flag, flag2, flag3); } _frontageClearance = null; LogWarning("Fish Warehouse will continue without loading docks; frontage clearance could not be established safely."); } _loadingDocks = new FishWarehouseLoadingDockHost(); if (_frontageClearance == null || !_loadingDocks.TryAttach(_property, Log)) { _loadingDocks.Dispose(); _loadingDocks = null; if (_frontageClearance != null) { _frontageClearanceCleanupPending = !_frontageClearance.Dispose(Log); if (_frontageClearanceCleanupPending) { return FailAndCleanup(stage, "Frontage clearance cleanup remained partial after loading dock setup failed.", flag, flag2, flag3); } _frontageClearance = null; } LogWarning("Fish Warehouse will continue without loading docks; existing Property features remain available."); } stage = "property-registration"; RegisterPropertyCollections(_property); flag = (Object)(object)FindProperty(definition.PropertyCode) == (Object)(object)_property; if (!flag) { return FailAndCleanup(stage, "Temporary Property was not observable in the vanilla Property collection.", flag, flag2, flag3); } stage = "network-initialization"; _property.InitializeSaveable(); _root.SetActive(true); byte b = 0; _networkObject.UpdateNetworkBehaviours((NetworkObject)null, ref b); ((NetworkBehaviour)_property).NetworkInitializeIfDisabled(); stage = "network-registration"; _bucket = _networkManager.GetPrefabObjects(definition.RuntimeCollectionId, true); if (_bucket == null) { return FailAndCleanup(stage, "Runtime prefab collection creation returned null.", flag, flag2, flag3); } int num = ReadInt(() => _bucket.GetObjectCount()); _bucket.AddObject(_networkObject, true); flag2 = ReadInt(() => _bucket.GetObjectCount()) > num; if (!flag2) { return FailAndCleanup(stage, "Runtime prefab registration did not increase the collection count.", flag, flag2, flag3); } stage = "spawn"; _serverManager.Spawn(_networkObject, (NetworkConnection)null, _root.scene); bool flag4 = Read(() => _networkObject.IsSpawned); bool flag5 = Read(() => _networkObject.IsServerInitialized); bool value = Read(() => ((NetworkBehaviour)_property).IsNetworked); bool value2 = Read(() => ((NetworkBehaviour)_property).IsClientInitialized); bool value3 = Read(() => ((NetworkBehaviour)_property).IsServerInitialized); bool flag6 = Read(() => ((NetworkBehaviour)_property).IsSpawned); flag3 = flag4 && flag5 && flag6; if (!flag3) { string text = $"networkObjectSpawned={flag4}, networkObjectServerInitialized={flag5}, propertyNetworked={value}, propertyClientInitialized={value2}, propertyServerInitialized={value3}, propertySpawned={flag6}"; return FailAndCleanup(stage, "Spawn returned without a spawned/server-initialized Property root (" + text + ").", flag, flag2, flag3); } return BuildResult(succeeded: true, "spawn", flag, flag2, flag3, cleanupPassed: true, null); } catch (Exception ex) { return FailAndCleanup(stage, ex.ToString(), flag, flag2, flag3); } } public RuntimePropertyHostResult Stop() { if (_loadingDocks != null && !_loadingDocks.CanUnload()) { return new RuntimePropertyHostResult(Succeeded: false, "occupied-loading-docks", PropertyRegistered: true, NetworkRegistrationPassed: true, SpawnPassed: true, DespawnPassed: false, CleanupPassed: true, AuthoredCollectionUnchanged: true, DocksNetworkIdentityUnchanged: true, "A Fish Warehouse loading dock is occupied or reserved by an active delivery."); } return StopAfterNavigationCleanup(); } private RuntimePropertyHostResult StopAfterNavigationCleanup() { if (_root == null && _property == null && _bucket == null && _frontageClearance == null) { return new RuntimePropertyHostResult(Succeeded: true, "already-stopped", PropertyRegistered: false, NetworkRegistrationPassed: false, SpawnPassed: false, DespawnPassed: true, CleanupPassed: true, AuthoredCollectionUnchanged: true, DocksNetworkIdentityUnchanged: true, null); } bool despawnPassed = true; bool flag = true; bool flag2 = _property != null && (Object)(object)FindProperty(_definition?.PropertyCode ?? string.Empty) == (Object)(object)_property; if (_continuousNavigationCoordinator != null && !_continuousNavigationCoordinator.Stop()) { _employeeNavigationState = FishWarehouseContinuousNavigationState.Failed; return new RuntimePropertyHostResult(Succeeded: false, "pending-native-navigation-cleanup", flag2, _bucket != null, _networkObject != null && Read(() => _networkObject.IsSpawned), DespawnPassed: false, CleanupPassed: false, AuthoredCollectionUnchanged: true, DocksNetworkIdentityUnchanged: true, "Fish Warehouse native navigation cleanup is pending; invoke Stop again to retry before property teardown."); } try { _continuousNavigationCoordinator = null; _employeeNavigationState = FishWarehouseContinuousNavigationState.Inactive; _garageOpening = null; _navigation = null; DisposeManagementWorldspaceUiContainer(); _loadingDocks?.Dispose(); _loadingDocks = null; _employeeInfrastructure?.Dispose(); _employeeInfrastructure = null; _propertyBounds?.Dispose(); _propertyBounds = null; _buildSurface?.Dispose(); _buildSurface = null; _interiorRoom?.Dispose(); _interiorRoom = null; if (_networkObject != null && Read(() => _networkObject.IsSpawned)) { if (_serverManager == null || _root == null) { throw new InvalidOperationException("ServerManager or runtime root was unavailable for despawn."); } _serverManager.Despawn(_root, new Nullable((DespawnType)0)); despawnPassed = !Read(() => _networkObject.IsSpawned); } } catch (Exception value) { despawnPassed = false; flag = false; LogWarning($"Runtime Property despawn failed: {value}"); } finally { if (_frontageClearance != null) { try { bool flag3 = _frontageClearance.Dispose(Log); flag = flag && flag3; _frontageClearanceCleanupPending = !flag3; } catch (Exception value2) { flag = false; _frontageClearanceCleanupPending = true; LogWarning($"Runtime Property frontage clearance restoration failed: {value2}"); } if (!_frontageClearanceCleanupPending) { _frontageClearance = null; } } } if (_property != null) { try { RemovePropertyCollections(_property); flag2 = (Object)(object)FindProperty(_definition?.PropertyCode ?? string.Empty) == (Object)(object)_property; flag = flag && !flag2; } catch (Exception value3) { flag = false; LogWarning($"Runtime Property collection cleanup failed: {value3}"); } } if (_bucket != null && _networkManager != null && (object)_definition != null) { try { flag &= _networkManager.RemoveSpawnableCollection(_definition.RuntimeCollectionId); } catch (Exception value4) { flag = false; LogWarning($"Runtime Property bucket cleanup failed: {value4}"); } } if (_root != null) { try { Object.DestroyImmediate((Object)(object)_root); } catch (Exception value5) { flag = false; LogWarning($"Runtime Property root cleanup failed: {value5}"); } } bool authoredCollectionUnchanged = _networkManager == null || _authoredCollection == null || _authoredCollection == _networkManager.SpawnablePrefabs; bool docksNetworkIdentityUnchanged = true; Property val = FindProperty("dockswarehouse"); if (val != null) { NetworkObject val2 = ResolveNetworkObject(val); docksNetworkIdentityUnchanged = val2 != null && _docksBefore == CaptureDocksIdentity(val2); } RuntimePropertyHostResult result = new RuntimePropertyHostResult(Succeeded: false, "cleanup", !flag2, _bucket != null, SpawnPassed: false, despawnPassed, flag, authoredCollectionUnchanged, docksNetworkIdentityUnchanged, flag ? null : "Runtime Property cleanup was partial."); Clear(); return result; } public bool TrySetOwned() { if (_property == null || (object)_definition == null || _networkObject == null) { LogWarning("Fish Warehouse ownership gate rejected: runtime Property is not active."); return false; } if (!Read(() => _networkObject.IsSpawned) || !Read(() => _networkObject.IsServerInitialized)) { LogWarning("Fish Warehouse ownership gate rejected: runtime Property NetworkObject is not spawned/server-initialized."); return false; } Property property = Singleton.Instance.GetProperty(_definition.PropertyCode); Property[] array = SnapshotProperties(Property.OwnedProperties); Property[] array2 = SnapshotProperties(Property.UnownedProperties); Property val = FindProperty("dockswarehouse"); if ((Object)(object)property != (Object)(object)_property || Read(() => _property.IsOwned) || !array2.Contains(_property) || array.Contains(_property) || val == null) { LogWarning("Fish Warehouse ownership gate rejected: ownership preconditions were not satisfied."); return false; } try { _property.SetOwned(); } catch (Exception value) { LogWarning($"Fish Warehouse Property.SetOwned() failed: {value}"); return false; } Property[] array3 = SnapshotProperties(Property.OwnedProperties); Property[] array4 = SnapshotProperties(Property.UnownedProperties); Property property2 = Singleton.Instance.GetProperty(_definition.PropertyCode); Property val2 = FindProperty("dockswarehouse"); bool flag = val2 != null && (Object)(object)val2 == (Object)(object)val && string.Equals(val2.PropertyCode, val.PropertyCode, StringComparison.OrdinalIgnoreCase) && val2.IsOwned == val.IsOwned; bool num = Read(() => _property.IsOwned) && SnapshotProperties(Property.Properties).Contains(_property) && array3.Contains(_property) && !array4.Contains(_property) && (Object)(object)property2 == (Object)(object)_property && array3.Length == array.Length + 1 && array4.Length == array2.Length - 1 && flag; if (num) { FishWarehouseManagementCanvasPatch.SetTargetProperty(_property); if (!EnsureManagementWorldspaceUiContainer()) { LogWarning("Fish Warehouse ownership succeeded, but its management worldspace UI container is unavailable."); } if (_propertyBounds == null || !_propertyBounds.EnsureActive()) { LogWarning("Fish Warehouse ownership succeeded, but its authored Property bounds are unavailable."); } if (_buildSurface == null) { _buildSurface = new FishWarehouseBuildSurfaceHost(); } bool num2 = _buildSurface.TryAttach(_property, _anchor, _definition.BuildGridRootName, _definition.BuildGridGuid, Log); if (!num2) { LogWarning("Fish Warehouse ownership succeeded, but the native build Grid was not attached."); } if (num2) { FishWarehouseEmployeeInfrastructureHost? employeeInfrastructure = _employeeInfrastructure; if (employeeInfrastructure != null && employeeInfrastructure.IsReady) { if (_continuousNavigationCoordinator == null) { _continuousNavigationCoordinator = new FishWarehouseContinuousNavigationCoordinator(new RuntimePropertyNavigationActions(this)); } _employeeNavigationState = FishWarehouseContinuousNavigationState.Inactive; goto IL_02ef; } } _employeeNavigationState = FishWarehouseContinuousNavigationState.Failed; LogWarning("Fish Warehouse ownership succeeded, but employee navigation is unavailable because the native build surface or employee infrastructure did not restore."); goto IL_02ef; } LogWarning("Fish Warehouse ownership gate did not pass its collection or Docks Warehouse invariants. No direct save write was performed."); return num; IL_02ef: Log("Fish Warehouse runtime Property is owned in memory. No direct save write was performed; a normal game save persists the Organized Crime sidecar."); return num; } public void UpdateOwnedFeatures() { if (!IsAlive((Object?)(object)_property)) { return; } Property? property = _property; if (property == null || !property.IsOwned || _continuousNavigationCoordinator == null || (_persistenceObjectReplayRequired && !_persistenceObjectsReplayed)) { return; } try { float realtimeSinceStartup = Time.realtimeSinceStartup; if (_continuousNavigationCoordinator.State == FishWarehouseContinuousNavigationState.Inactive) { _continuousNavigationCoordinator.Start(realtimeSinceStartup); } else { _continuousNavigationCoordinator.Tick(realtimeSinceStartup); } UpdateDockDeliveryBridge(); UpdateGarageCrossingAssist(); } catch (Exception ex) { _employeeNavigationState = FishWarehouseContinuousNavigationState.Failed; LogWarning("Fish Warehouse employee navigation failed safely before completion: " + ex.Message); } } private void UpdateDockDeliveryBridge() { if (IsAlive((Object?)(object)_property)) { if (_dockDeliveryBridge == null) { _dockDeliveryBridge = new FishWarehouseDockDeliveryBridgeHost(); } _dockDeliveryBridge.Tick(_property, hostAuthority: true, Log); } } private void UpdateGarageCrossingAssist() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) FishWarehouseContinuousNavigationCoordinator? continuousNavigationCoordinator = _continuousNavigationCoordinator; if (continuousNavigationCoordinator != null && continuousNavigationCoordinator.State == FishWarehouseContinuousNavigationState.Ready && IsAlive((Object?)(object)_property)) { if (_garageCrossing == null) { _garageCrossing = new FishWarehouseGarageCrossingHost(); } if (!_garageCrossing.IsActive) { _garageCrossing.Activate(ComputeGarageWorldCenter()); } _garageCrossing.Tick(_property, Time.deltaTime, hostAuthority: true); } } private Vector3 ComputeGarageWorldCenter() { //IL_004d: 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_00a0: 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) FishWarehouseGarageOpeningProjection fishWarehouseGarageOpeningProjection = _garageOpening?.Preflight?.Projection; if ((object)fishWarehouseGarageOpeningProjection == null || !IsAlive((Object?)(object)_anchor)) { if (!IsAlive((Object?)(object)_property)) { return Vector3.zero; } return ((Component)_property).transform.position; } FishWarehouseTransitionPrism transitionPrism = fishWarehouseGarageOpeningProjection.TransitionPrism; Vector3 val = default(Vector3); ((Vector3)(ref val))..ctor((transitionPrism.MinimumX + transitionPrism.MaximumX) / 2f, (transitionPrism.MinimumY + transitionPrism.MaximumY) / 2f, (transitionPrism.MinimumZ + transitionPrism.MaximumZ) / 2f); return _anchor.TransformPoint(val); } public bool TryPlaceEmployeeHome() { if (_property == null || _buildSurface == null || !_property.IsOwned) { LogWarning("Fish Warehouse employee-home placement rejected: the runtime Property is not owned or its native Grid is unavailable."); return false; } return _buildSurface.TryPlaceEmployeeHome(_property, FishWarehouseEmployeeHomeDefinition.Default, Log); } public void ConfigurePersistenceReplay(bool objectReplayRequired, int? employeeAgentTypeId) { _persistenceObjectReplayRequired = objectReplayRequired; _persistenceObjectsReplayed = !objectReplayRequired; _persistenceEmployeeAgentTypeId = employeeAgentTypeId; } public void MarkPersistenceObjectsReplayed() { _persistenceObjectsReplayed = true; } public bool TryCreateEmployeeReplayAdapter(PropertyData propertyData, out IFishWarehouseNativeEmployeeReplayAdapter adapter, out string? failureReason, IReadOnlyCollection? inventoryRestoredEmployeeGuids = null) { adapter = null; failureReason = null; if (!PersistenceRuntimeReady || (_persistenceObjectReplayRequired && !_persistenceObjectsReplayed)) { failureReason = "Fish Warehouse employee replay adapter requires a ready runtime Property after object replay."; return false; } EmployeeManager instance = NetworkSingleton.Instance; if (!IsAlive((Object?)(object)instance)) { failureReason = "Fish Warehouse employee replay adapter requires a live EmployeeManager."; return false; } adapter = new FishWarehouseNativeEmployeeReplayAdapter(propertyData, _property, instance, inventoryRestoredEmployeeGuids); return true; } public bool TryAdoptRestoredEmployeeHome() { return AssessRestoredEmployeeHome() == FishWarehouseEmployeeHomeAdoptionResult.Adopted; } public FishWarehouseEmployeeHomeAdoptionResult AssessRestoredEmployeeHome(IReadOnlyCollection? savedHomeGuids = null) { if (!PersistenceRuntimeReady || (_persistenceObjectReplayRequired && !_persistenceObjectsReplayed)) { LogWarning("Fish Warehouse restored employee-home adoption rejected because object replay or persistence runtime readiness was incomplete."); return FishWarehouseEmployeeHomeAdoptionResult.Failed; } Property property = _property; FishWarehouseBuildSurfaceHost buildSurface = _buildSurface; if (!IsAlive((Object?)(object)property) || buildSurface == null) { return FishWarehouseEmployeeHomeAdoptionResult.Failed; } List<(BuildableItem, EmployeeHome)> list = new List<(BuildableItem, EmployeeHome)>(); try { foreach (EmployeeHome item in Object.FindObjectsOfType()) { if (IsAlive((Object?)(object)item)) { BuildableItem component = ((Component)item).GetComponent(); if (IsAlive((Object?)(object)component) && !((Object)(object)component.ParentProperty != (Object)(object)property)) { list.Add((component, item)); } } } } catch (Exception ex) { LogWarning("Fish Warehouse restored employee-home scan failed safely: " + ex.Message); return FishWarehouseEmployeeHomeAdoptionResult.Failed; } if (list.Count == 0) { return FishWarehouseEmployeeHomeAdoptionResult.NoneFound; } List list2 = new List(list.Count); foreach (var item2 in list) { list2.Add(ReadHomeGuid(item2.Item1)); } int? num = FishWarehouseEmployeeHomeAdoptionPlanner.SelectHomeIndex(list2, savedHomeGuids); if (!num.HasValue) { return FishWarehouseEmployeeHomeAdoptionResult.NoneFound; } (BuildableItem, EmployeeHome) tuple = list[num.Value]; if (!buildSurface.TryAdoptRestoredEmployeeHome(property, tuple.Item1, tuple.Item2, Log)) { return FishWarehouseEmployeeHomeAdoptionResult.Failed; } return FishWarehouseEmployeeHomeAdoptionResult.Adopted; } private static string? ReadHomeGuid(BuildableItem buildable) { //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) try { return ((object)buildable.GUID/*cast due to .constrained prefix*/).ToString(); } catch { return null; } } public void EnsureManagementWorldspaceUiContainerForOpenCanvas() { if (_property == null || !_property.IsOwned) { return; } try { ManagementWorldspaceCanvas obj = (Singleton.InstanceExists ? Singleton.Instance : null); if (obj == null || !obj.IsOpen) { _managementDiagnosticFailureReported = false; } else if (_property.WorldspaceUIContainer == null) { EnsureManagementWorldspaceUiContainer(); } } catch (Exception ex) { if (!_managementDiagnosticFailureReported) { _managementDiagnosticFailureReported = true; LogWarning("Fish Warehouse management worldspace UI repair failed safely: " + ex.Message); } } } private bool EnsureManagementWorldspaceUiContainer() { //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0075: Expected O, but got Unknown if (_property == null) { return false; } if (!FishWarehouseManagementEligibilityDiagnostic.ShouldCreatePropertyWorldspaceUiContainer(_property.IsOwned, _property.WorldspaceUIContainer != null)) { return _property.WorldspaceUIContainer != null; } GameObject val = null; try { if (!Singleton.InstanceExists) { return false; } ManagementWorldspaceCanvas instance = Singleton.Instance; if (((instance != null) ? instance.Canvas : null) == null) { return false; } val = new GameObject("Fish Warehouse Worldspace UI Container"); RectTransform val2 = val.AddComponent(); ((Transform)val2).SetParent(((Component)instance.Canvas).transform, false); val.SetActive(false); _property.WorldspaceUIContainer = val2; _managementWorldspaceUiContainer = val2; val = null; int num; if ((Object)(object)_property.WorldspaceUIContainer == (Object)(object)val2) { num = (((Object)(object)((Transform)val2).parent == (Object)(object)((Component)instance.Canvas).transform) ? 1 : 0); if (num != 0) { Log("Fish Warehouse management worldspace UI container enabled under the vanilla management canvas."); } } else { num = 0; } return (byte)num != 0; } catch (Exception value) { if (val != null) { Object.DestroyImmediate((Object)(object)val); } LogWarning($"Fish Warehouse management worldspace UI container failed safely: {value}"); return false; } } private void DisposeManagementWorldspaceUiContainer() { if (_property != null && (Object)(object)_property.WorldspaceUIContainer == (Object)(object)_managementWorldspaceUiContainer) { _property.WorldspaceUIContainer = null; } if (_managementWorldspaceUiContainer != null) { Object.DestroyImmediate((Object)(object)((Component)_managementWorldspaceUiContainer).gameObject); } _managementWorldspaceUiContainer = null; } private RuntimePropertyHostResult FailAndCleanup(string stage, string reason, bool propertyRegistered, bool networkRegistrationPassed, bool spawnPassed) { LogWarning("Fish Warehouse runtime Property stopped at " + stage + ": " + reason); RuntimePropertyHostResult runtimePropertyHostResult = Stop(); return new RuntimePropertyHostResult(Succeeded: false, stage, propertyRegistered, networkRegistrationPassed, spawnPassed, runtimePropertyHostResult.DespawnPassed, runtimePropertyHostResult.CleanupPassed, runtimePropertyHostResult.AuthoredCollectionUnchanged, runtimePropertyHostResult.DocksNetworkIdentityUnchanged, reason); } private RuntimePropertyHostResult BuildResult(bool succeeded, string stage, bool propertyRegistered, bool networkRegistrationPassed, bool spawnPassed, bool cleanupPassed, string? failureReason) { return new RuntimePropertyHostResult(succeeded, stage, propertyRegistered, networkRegistrationPassed, spawnPassed, DespawnPassed: false, cleanupPassed, AuthoredCollectionUnchanged: true, DocksNetworkIdentityUnchanged: true, failureReason); } private void Clear() { if (_continuousNavigationCoordinator != null && !_continuousNavigationCoordinator.Stop()) { LogWarning("Fish Warehouse native navigation cleanup remains pending; retaining navigation ownership for a later explicit Stop retry."); return; } _garageCrossing?.Deactivate(); _garageCrossing = null; _dockDeliveryBridge = null; _root = null; _networkObject = null; _property = null; _bucket = null; _networkManager = null; _serverManager = null; _authoredCollection = null; _anchor = null; _buildSurface = null; _interiorRoom = null; _garageOpening = null; _navigation = null; _continuousNavigationCoordinator = null; _employeeNavigationState = FishWarehouseContinuousNavigationState.Inactive; _propertyBounds = null; _employeeInfrastructure = null; if (!_frontageClearanceCleanupPending) { _frontageClearance = null; } _loadingDocks = null; _managementDiagnosticFailureReported = false; _managementWorldspaceUiContainer = null; FishWarehouseManagementCanvasPatch.SetTargetProperty(null); _definition = null; _persistenceObjectReplayRequired = false; _persistenceObjectsReplayed = false; _persistenceEmployeeAgentTypeId = null; _docksBefore = DocksIdentity.Empty; } private PackagingStation? FindRepresentativePackagingStation() { Property property = _property; if (!IsAlive((Object?)(object)property)) { return null; } try { foreach (PackagingStation item in Resources.FindObjectsOfTypeAll()) { if (IsAlive((Object?)(object)item)) { BuildableItem component = ((Component)item).GetComponent(); if (IsAlive((Object?)(object)component) && (Object)(object)component.ParentProperty == (Object)(object)property) { return item; } } } } catch (Exception ex) { LogWarning("Fish Warehouse representative packaging-station lookup failed safely: " + ex.Message); } return null; } private static Property? FindProperty(string code) { Property[] array = SnapshotProperties(Property.Properties); foreach (Property val in array) { if (string.Equals(val.PropertyCode, code, StringComparison.OrdinalIgnoreCase)) { return val; } } return null; } private static GameObject? FindAnchor(string path) { //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) foreach (GameObject item in Resources.FindObjectsOfTypeAll()) { if (item != null) { Scene scene = item.scene; if (((Scene)(ref scene)).IsValid() && string.Equals(GetTransformPath(item.transform), path, StringComparison.Ordinal)) { return item; } } } return null; } private static NetworkObject? ResolveNetworkObject(Property property) { try { if (((NetworkBehaviour)property).NetworkObject != null) { return ((NetworkBehaviour)property).NetworkObject; } } catch { } return ((Component)property).gameObject.GetComponent(); } private static void RegisterPropertyCollections(Property property) { if (!Property.Properties.Contains(property)) { Property.Properties.Add(property); } if (!Property.UnownedProperties.Contains(property)) { Property.UnownedProperties.Add(property); } } private static void RemovePropertyCollections(Property property) { Property.Properties.Remove(property); Property.OwnedProperties.Remove(property); Property.UnownedProperties.Remove(property); } private static Property[] SnapshotProperties(List? properties) { if (properties != null) { return Il2CppArrayBase.op_Implicit(properties.ToArray()); } return Array.Empty(); } private static DocksIdentity CaptureDocksIdentity(NetworkObject networkObject) { return new DocksIdentity(ReadInt(() => networkObject.ObjectId), ReadULong(() => networkObject.SceneId), ReadString(() => ((object)networkObject.State/*cast due to .constrained prefix*/).ToString())); } private static string GetTransformPath(Transform transform) { Stack stack = new Stack(); for (Transform val = transform; val != null; val = val.parent) { stack.Push(((Object)val).name); } return string.Join("/", stack); } private static void Log(string message) { MelonLogger.Msg("[Organized Crime] " + message); } private static void LogWarning(string message) { MelonLogger.Warning("[Organized Crime] " + message); } private static bool IsAlive(Object? value) { try { return value != null && value != (Object)null; } catch { return false; } } private static int GetNativeInstanceId(Object? value) { if (!IsAlive(value)) { return 0; } try { return value.GetInstanceID(); } catch { return 0; } } private static string ReadNativeId(Func read) { try { Object val = read(); if (!IsAlive(val)) { return ""; } return val.GetInstanceID().ToString(); } catch { return ""; } } private static bool Read(Func read) { try { return read(); } catch { return false; } } private static int ReadInt(Func read) { try { return read(); } catch { return 0; } } private static ulong ReadULong(Func read) { try { return read(); } catch { return 0uL; } } private static string ReadString(Func read) { try { return read(); } catch { return ""; } } } public sealed record RuntimePropertyHostResult(bool Succeeded, string Stage, bool PropertyRegistered, bool NetworkRegistrationPassed, bool SpawnPassed, bool DespawnPassed, bool CleanupPassed, bool AuthoredCollectionUnchanged, bool DocksNetworkIdentityUnchanged, string? FailureReason) { public static RuntimePropertyHostResult Failure(string stage, string reason) { return new RuntimePropertyHostResult(Succeeded: false, stage, PropertyRegistered: false, NetworkRegistrationPassed: false, SpawnPassed: false, DespawnPassed: false, CleanupPassed: true, AuthoredCollectionUnchanged: true, DocksNetworkIdentityUnchanged: true, reason); } } public interface IRuntimePropertyHost { bool PersistenceRuntimeReady { get; } FishWarehouseContinuousNavigationState EmployeeNavigationState { get; } bool EmployeeNavigationReady { get; } int? EmployeeNavigationAgentTypeId { get; } RuntimePropertyHostResult Start(RuntimePropertyDefinition definition); bool TrySetOwned(); void ConfigurePersistenceReplay(bool objectReplayRequired, int? employeeAgentTypeId); void MarkPersistenceObjectsReplayed(); bool TryAdoptRestoredEmployeeHome(); void UpdateOwnedFeatures(); RuntimePropertyHostResult Stop(); } public static class RuntimePropertyLifecycle { public static bool CanTransition(RuntimePropertyLifecycleState from, RuntimePropertyLifecycleState to) { switch (from) { case RuntimePropertyLifecycleState.Unavailable: if (to != RuntimePropertyLifecycleState.Ready) { break; } return true; case RuntimePropertyLifecycleState.Ready: if (to != RuntimePropertyLifecycleState.Starting) { break; } return true; case RuntimePropertyLifecycleState.Starting: switch (to) { case RuntimePropertyLifecycleState.Spawned: return true; case RuntimePropertyLifecycleState.Failed: return true; } break; case RuntimePropertyLifecycleState.Spawned: if (to != RuntimePropertyLifecycleState.Unloading) { break; } return true; case RuntimePropertyLifecycleState.Unloading: switch (to) { case RuntimePropertyLifecycleState.Spawned: return true; case RuntimePropertyLifecycleState.Unavailable: return true; case RuntimePropertyLifecycleState.Failed: return true; } break; } return false; } } public enum RuntimePropertyLifecycleState { Unavailable, Ready, Starting, Spawned, Unloading, Failed } public static class RuntimePropertyReadiness { public static bool IsFishWarehouseReady() { if (!FindAnchor(RuntimePropertyDefinition.FishWarehouse.AnchorPath)) { return false; } if (Property.Properties == null || Property.Properties.Count == 0) { return false; } Property val = FindProperty("dockswarehouse"); if (val == null) { return false; } NetworkObject val2; try { val2 = ((NetworkBehaviour)val).NetworkObject; } catch { val2 = ((Component)val).gameObject.GetComponent(); } ServerManager serverManager = InstanceFinder.ServerManager; if (((val2 != null) ? val2.NetworkManager : null) != null && serverManager != null) { return Read(() => serverManager.OneServerStarted()); } return false; } private static bool FindAnchor(string path) { //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) foreach (GameObject item in Resources.FindObjectsOfTypeAll()) { if (item != null) { Scene scene = item.scene; if (((Scene)(ref scene)).IsValid() && string.Equals(GetTransformPath(item.transform), path, StringComparison.Ordinal)) { return true; } } } return false; } private static Property? FindProperty(string code) { Enumerator enumerator = Property.Properties.GetEnumerator(); while (enumerator.MoveNext()) { Property current = enumerator.Current; if (current != null && string.Equals(current.PropertyCode, code, StringComparison.OrdinalIgnoreCase)) { return current; } } return null; } private static string GetTransformPath(Transform transform) { Stack stack = new Stack(); for (Transform val = transform; val != null; val = val.parent) { stack.Push(((Object)val).name); } return string.Join("/", stack); } private static bool Read(Func read) { try { return read(); } catch { return false; } } } public sealed class S1ApiRelease1NativePresentation : IRelease1NativePresentation { private readonly Release1QuestPersistencePolicy _policy; private readonly Action _log; private readonly HashSet _loggedMessages = new HashSet(StringComparer.Ordinal); private readonly string _contactId; private readonly Func _createContact; private readonly Func _isContact; private NPC? _contact; private IRelease1MessagingNpc? _messaging; private Release1SmallCourtesyQuest? _smallCourtesyQuest; private Release1WrongAddressQuest? _wrongAddressQuest; private Release1RoomWithNoNameQuest? _roomWithNoNameQuest; private Release1ShortNoticeQuest? _shortNoticeQuest; private Release1KeepTheLightsOffQuest? _keepTheLightsOffQuest; private Release1TheEnvelopeQuest? _theEnvelopeQuest; private string? _decisionId; private Action? _onChosen; public S1ApiRelease1NativePresentation(Release1QuestPersistencePolicy policy, Action? log = null) : this(policy, "oc_release1_nell", () => (NPC)(object)new Release1NellNpc(), (NPC npc) => npc is Release1NellNpc, log) { } public S1ApiRelease1NativePresentation(Release1QuestPersistencePolicy policy, string contactId, Func createContact, Func isContact, Action? log = null) { _policy = policy; _contactId = contactId ?? throw new ArgumentNullException("contactId"); _createContact = createContact ?? throw new ArgumentNullException("createContact"); _isContact = isContact ?? throw new ArgumentNullException("isContact"); _log = log ?? ((Action)delegate { }); } public Release1NativePresentationStatus TryEnsureContact() { try { if (_contact != null) { return Release1NativePresentationStatus.Succeeded; } switch (Release1NativePresentationSupport.ClassifyContact(SelectContactCandidates(), _contactId)) { case Release1ContactResolutionOutcome.Adopt: _contact = FindContactById(_contactId); _messaging = _contact as IRelease1MessagingNpc; return (_contact == null || _messaging == null) ? Release1NativePresentationStatus.Unavailable : Release1NativePresentationStatus.Succeeded; case Release1ContactResolutionOutcome.WrongType: LogOnce("Release 1 native presentation found an existing NPC with the id '" + _contactId + "' that is not this mod's contact type; refusing to create a second contact."); return Release1NativePresentationStatus.Faulted; default: _messaging = (_contact = _createContact()) as IRelease1MessagingNpc; return Release1NativePresentationStatus.Succeeded; } } catch (Exception ex) { LogOnce("Release 1 native presentation contact ensure faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TryReadSentMessages(out IReadOnlyList texts) { //IL_0052: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Invalid comparison between Unknown and I4 texts = Array.Empty(); if (_contact == null) { return Release1NativePresentationStatus.Unavailable; } try { MSGConversation val = ResolveConversation(_contact); if (val == null) { return Release1NativePresentationStatus.Unavailable; } List messageHistory = val.messageHistory; if (messageHistory == null) { return Release1NativePresentationStatus.Unavailable; } List list = new List(); Enumerator enumerator = messageHistory.GetEnumerator(); while (enumerator.MoveNext()) { Message current = enumerator.Current; if (current != null && (int)current.sender == 1) { list.Add(current.text); } } texts = Release1NativePresentationSupport.NormalizeSentTexts(list); return Release1NativePresentationStatus.Succeeded; } catch (Exception ex) { LogOnce("Release 1 native presentation read sent messages faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TryReadDecision(out Release1ObservedDecision? decision) { decision = null; if (_contact == null) { return Release1NativePresentationStatus.Unavailable; } try { IReadOnlyList readOnlyList = ReadNativeResponseLabels(_contact); if (readOnlyList.Count == 0) { return Release1NativePresentationStatus.Succeeded; } string prompt = ReadMostRecentNpcMessageText(_contact); decision = new Release1ObservedDecision(_decisionId, readOnlyList, prompt); return Release1NativePresentationStatus.Succeeded; } catch (Exception ex) { LogOnce("Release 1 native presentation read decision faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TrySendMessage(string text) { try { ArgumentNullException.ThrowIfNull(text, "text"); return SendNative(Release1PlayerCopy.Normalize(text), null); } catch (Exception ex) { LogOnce("Release 1 native presentation send message faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TrySetDecision(Release1DesiredDecision decision, Action onChosen) { //IL_018d: 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_019a: 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_01ba: Expected O, but got Unknown try { ArgumentNullException.ThrowIfNull(decision, "decision"); ArgumentNullException.ThrowIfNull(onChosen, "onChosen"); if (_contact == null || _messaging == null) { return Release1NativePresentationStatus.Unavailable; } Dictionary dictionary = new Dictionary(StringComparer.Ordinal); List list = new List(); foreach (Release1DecisionOption option in decision.Options) { string text = Release1PlayerCopy.Normalize(option.Label); dictionary[text] = option.Command; list.Add(text); } if (Release1NativePresentationSupport.ShouldBindExistingResponses(ReadNativeResponseLabels(_contact), list)) { HashSet availableLabels = new HashSet(_messaging.LoadedResponses.Keys, StringComparer.Ordinal); if (!Release1NativePresentationSupport.AllDesiredLabelsAvailable(list, availableLabels)) { LogOnce("Release 1 native presentation could not find a restored response for every desired decision option; deferring the bind to a later reconcile pass."); return Release1NativePresentationStatus.Unavailable; } foreach (string item in list) { if (Release1NativePresentationSupport.TryMatchRebindCommand(dictionary, item, out var command) && _messaging.TryGetResponse(item, out Response response) && response != null) { response.OnTriggered = delegate { InvokeChosen(command); }; } } } else { List list2 = new List(); foreach (string item2 in list) { Release1PresentationCommand command2 = dictionary[item2]; list2.Add(new Response { Label = item2, Text = item2, OnTriggered = delegate { InvokeChosen(command2); } }); } Release1NativePresentationStatus release1NativePresentationStatus = SendNative(Release1PlayerCopy.Normalize(decision.Prompt), list2.ToArray()); if (release1NativePresentationStatus != Release1NativePresentationStatus.Succeeded) { return release1NativePresentationStatus; } } _decisionId = decision.Id; _onChosen = onChosen; return Release1NativePresentationStatus.Succeeded; } catch (Exception ex) { LogOnce("Release 1 native presentation set decision faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TryClearDecision() { try { Release1NativePresentationStatus num = ClearNativeResponses(); if (num == Release1NativePresentationStatus.Succeeded) { _decisionId = null; _onChosen = null; } return num; } catch (Exception ex) { LogOnce("Release 1 native presentation clear decision faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TryReadQuest(string key, out Release1ObservedQuest? quest) { quest = null; try { switch (key) { case "release1.small-courtesy": { if (!TryResolveSmallCourtesyQuest(out Release1SmallCourtesyQuest quest4)) { return Release1NativePresentationStatus.Faulted; } if (quest4 == null) { return Release1NativePresentationStatus.Succeeded; } quest = new Release1ObservedQuest(key, quest4.ReadEntryStates()); return Release1NativePresentationStatus.Succeeded; } case "release1.wrong-address": { if (!TryResolveWrongAddressQuest(out Release1WrongAddressQuest quest6)) { return Release1NativePresentationStatus.Faulted; } if (quest6 == null) { return Release1NativePresentationStatus.Succeeded; } quest = new Release1ObservedQuest(key, quest6.ReadEntryStates()); return Release1NativePresentationStatus.Succeeded; } case "release1.room-with-no-name": { if (!TryResolveRoomWithNoNameQuest(out Release1RoomWithNoNameQuest quest5)) { return Release1NativePresentationStatus.Faulted; } if (quest5 == null) { return Release1NativePresentationStatus.Succeeded; } quest = new Release1ObservedQuest(key, quest5.ReadEntryStates()); return Release1NativePresentationStatus.Succeeded; } case "release1.short-notice": { if (!TryResolveShortNoticeQuest(out Release1ShortNoticeQuest quest3)) { return Release1NativePresentationStatus.Faulted; } if (quest3 == null) { return Release1NativePresentationStatus.Succeeded; } quest = new Release1ObservedQuest(key, quest3.ReadEntryStates()); return Release1NativePresentationStatus.Succeeded; } case "release1.keep-the-lights-off": { if (!TryResolveKeepTheLightsOffQuest(out Release1KeepTheLightsOffQuest quest7)) { return Release1NativePresentationStatus.Faulted; } if (quest7 == null) { return Release1NativePresentationStatus.Succeeded; } quest = new Release1ObservedQuest(key, quest7.ReadEntryStates()); return Release1NativePresentationStatus.Succeeded; } case "release1.the-envelope": { if (!TryResolveTheEnvelopeQuest(out Release1TheEnvelopeQuest quest2)) { return Release1NativePresentationStatus.Faulted; } if (quest2 == null) { return Release1NativePresentationStatus.Succeeded; } quest = new Release1ObservedQuest(key, quest2.ReadEntryStates()); return Release1NativePresentationStatus.Succeeded; } default: return Release1NativePresentationStatus.Succeeded; } } catch (Exception ex) { LogOnce("Release 1 native presentation read quest faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TryApplyQuest(Release1DesiredQuest quest) { try { ArgumentNullException.ThrowIfNull(quest, "quest"); switch (quest.Key) { case "release1.small-courtesy": { if (!TryResolveSmallCourtesyQuest(out Release1SmallCourtesyQuest quest6)) { return Release1NativePresentationStatus.Faulted; } if (quest6 == null) { quest6 = CreateQuest(); } if (quest6 == null) { return Release1NativePresentationStatus.Unavailable; } _smallCourtesyQuest = quest6; quest6.Project(quest); return Release1NativePresentationStatus.Succeeded; } case "release1.wrong-address": { if (!TryResolveWrongAddressQuest(out Release1WrongAddressQuest quest7)) { return Release1NativePresentationStatus.Faulted; } if (quest7 == null) { quest7 = CreateQuest(); } if (quest7 == null) { return Release1NativePresentationStatus.Unavailable; } _wrongAddressQuest = quest7; quest7.Project(quest); return Release1NativePresentationStatus.Succeeded; } case "release1.room-with-no-name": { if (!TryResolveRoomWithNoNameQuest(out Release1RoomWithNoNameQuest quest5)) { return Release1NativePresentationStatus.Faulted; } if (quest5 == null) { quest5 = CreateQuest(); } if (quest5 == null) { return Release1NativePresentationStatus.Unavailable; } _roomWithNoNameQuest = quest5; quest5.Project(quest); return Release1NativePresentationStatus.Succeeded; } case "release1.short-notice": { if (!TryResolveShortNoticeQuest(out Release1ShortNoticeQuest quest3)) { return Release1NativePresentationStatus.Faulted; } if (quest3 == null) { quest3 = CreateQuest(); } if (quest3 == null) { return Release1NativePresentationStatus.Unavailable; } _shortNoticeQuest = quest3; quest3.Project(quest); return Release1NativePresentationStatus.Succeeded; } case "release1.keep-the-lights-off": { if (!TryResolveKeepTheLightsOffQuest(out Release1KeepTheLightsOffQuest quest4)) { return Release1NativePresentationStatus.Faulted; } if (quest4 == null) { quest4 = CreateQuest(); } if (quest4 == null) { return Release1NativePresentationStatus.Unavailable; } _keepTheLightsOffQuest = quest4; quest4.Project(quest); return Release1NativePresentationStatus.Succeeded; } case "release1.the-envelope": { if (!TryResolveTheEnvelopeQuest(out Release1TheEnvelopeQuest quest2)) { return Release1NativePresentationStatus.Faulted; } if (quest2 == null) { quest2 = CreateQuest(); } if (quest2 == null) { return Release1NativePresentationStatus.Unavailable; } _theEnvelopeQuest = quest2; quest2.Project(quest); return Release1NativePresentationStatus.Succeeded; } default: return Release1NativePresentationStatus.Rejected; } } catch (Exception ex) { LogOnce("Release 1 native presentation apply quest faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public Release1NativePresentationStatus TryEndQuest(string key) { try { switch (key) { case "release1.small-courtesy": { if (!TryResolveSmallCourtesyQuest(out Release1SmallCourtesyQuest quest3)) { return Release1NativePresentationStatus.Faulted; } if (quest3 == null) { return Release1NativePresentationStatus.Succeeded; } ((Quest)quest3).End(); _smallCourtesyQuest = null; return Release1NativePresentationStatus.Succeeded; } case "release1.wrong-address": { if (!TryResolveWrongAddressQuest(out Release1WrongAddressQuest quest5)) { return Release1NativePresentationStatus.Faulted; } if (quest5 == null) { return Release1NativePresentationStatus.Succeeded; } ((Quest)quest5).End(); _wrongAddressQuest = null; return Release1NativePresentationStatus.Succeeded; } case "release1.room-with-no-name": { if (!TryResolveRoomWithNoNameQuest(out Release1RoomWithNoNameQuest quest4)) { return Release1NativePresentationStatus.Faulted; } if (quest4 == null) { return Release1NativePresentationStatus.Succeeded; } ((Quest)quest4).End(); _roomWithNoNameQuest = null; return Release1NativePresentationStatus.Succeeded; } case "release1.short-notice": { if (!TryResolveShortNoticeQuest(out Release1ShortNoticeQuest quest2)) { return Release1NativePresentationStatus.Faulted; } if (quest2 == null) { return Release1NativePresentationStatus.Succeeded; } ((Quest)quest2).End(); _shortNoticeQuest = null; return Release1NativePresentationStatus.Succeeded; } case "release1.keep-the-lights-off": { if (!TryResolveKeepTheLightsOffQuest(out Release1KeepTheLightsOffQuest quest6)) { return Release1NativePresentationStatus.Faulted; } if (quest6 == null) { return Release1NativePresentationStatus.Succeeded; } ((Quest)quest6).End(); _keepTheLightsOffQuest = null; return Release1NativePresentationStatus.Succeeded; } case "release1.the-envelope": { if (!TryResolveTheEnvelopeQuest(out Release1TheEnvelopeQuest quest)) { return Release1NativePresentationStatus.Faulted; } if (quest == null) { return Release1NativePresentationStatus.Succeeded; } ((Quest)quest).End(); _theEnvelopeQuest = null; return Release1NativePresentationStatus.Succeeded; } default: return Release1NativePresentationStatus.Succeeded; } } catch (Exception ex) { LogOnce("Release 1 native presentation end quest faulted: " + ex.GetType().Name); return Release1NativePresentationStatus.Faulted; } } public void OnPreLoad() { _contact = null; _messaging = null; _smallCourtesyQuest = null; _wrongAddressQuest = null; _roomWithNoNameQuest = null; _shortNoticeQuest = null; _keepTheLightsOffQuest = null; _theEnvelopeQuest = null; _decisionId = null; _onChosen = null; } public void OnSaveStart() { try { if (_policy == Release1QuestPersistencePolicy.DisposablePerLoad) { TryEndQuest("release1.small-courtesy"); TryEndQuest("release1.wrong-address"); TryEndQuest("release1.room-with-no-name"); TryEndQuest("release1.short-notice"); TryEndQuest("release1.keep-the-lights-off"); TryEndQuest("release1.the-envelope"); } } catch (Exception ex) { LogOnce("Release 1 native presentation OnSaveStart faulted: " + ex.GetType().Name); } } private void InvokeChosen(Release1PresentationCommand command) { _onChosen?.Invoke(command); } private Release1NativePresentationStatus SendNative(string text, Response[]? responses) { if (_contact == null) { return Release1NativePresentationStatus.Unavailable; } _contact.SendTextMessage(text, responses, 1f, false); return Release1NativePresentationStatus.Succeeded; } private Release1NativePresentationStatus ClearNativeResponses() { if (_contact == null) { return Release1NativePresentationStatus.Unavailable; } MSGConversation val = ResolveConversation(_contact); if (val == null) { return Release1NativePresentationStatus.Unavailable; } val.ClearResponses(false); return Release1NativePresentationStatus.Succeeded; } private bool TryResolveSmallCourtesyQuest(out Release1SmallCourtesyQuest? quest) { bool result = TryResolveQuest(ref _smallCourtesyQuest, "Small Courtesy"); quest = _smallCourtesyQuest; return result; } private bool TryResolveWrongAddressQuest(out Release1WrongAddressQuest? quest) { bool result = TryResolveQuest(ref _wrongAddressQuest, "Wrong Address"); quest = _wrongAddressQuest; return result; } private bool TryResolveRoomWithNoNameQuest(out Release1RoomWithNoNameQuest? quest) { bool result = TryResolveQuest(ref _roomWithNoNameQuest, "A Room With No Name"); quest = _roomWithNoNameQuest; return result; } private bool TryResolveShortNoticeQuest(out Release1ShortNoticeQuest? quest) { bool result = TryResolveQuest(ref _shortNoticeQuest, "Short Notice"); quest = _shortNoticeQuest; return result; } private bool TryResolveKeepTheLightsOffQuest(out Release1KeepTheLightsOffQuest? quest) { bool result = TryResolveQuest(ref _keepTheLightsOffQuest, "Keep the Lights Off"); quest = _keepTheLightsOffQuest; return result; } private bool TryResolveTheEnvelopeQuest(out Release1TheEnvelopeQuest? quest) { bool result = TryResolveQuest(ref _theEnvelopeQuest, "The Envelope"); quest = _theEnvelopeQuest; return result; } private bool TryResolveQuest(ref TQuest? cache, string displayTitle) where TQuest : Quest { if (cache != null) { return true; } Quest questByName = QuestManager.GetQuestByName(displayTitle); switch (Release1NativePresentationSupport.ClassifyQuestLookup(questByName != null, questByName is TQuest)) { case Release1QuestResolutionOutcome.WrongType: LogOnce($"Release 1 native presentation found an existing quest titled '{displayTitle}' that is not a {typeof(TQuest).Name}; refusing to create a second quest."); return false; case Release1QuestResolutionOutcome.Found: cache = (TQuest)(object)questByName; return true; default: return true; } } private static TQuest CreateQuest() where TQuest : Quest { return (TQuest)(object)QuestManager.CreateQuest(typeof(TQuest), (string)null); } private IEnumerable<(string? Id, bool IsContact)> SelectContactCandidates() { foreach (NPC item in NPC.All) { yield return (Id: item.ID, IsContact: _isContact(item)); } } private NPC? FindContactById(string id) { foreach (NPC item in NPC.All) { if (_isContact(item) && string.Equals(item.ID, id, StringComparison.Ordinal)) { return item; } } return null; } private static IReadOnlyList ReadNativeResponseLabels(NPC contact) { MSGConversation val = ResolveConversation(contact); if (val == null) { return Array.Empty(); } List currentResponses = val.currentResponses; if (currentResponses == null) { return Array.Empty(); } List list = new List(); Enumerator enumerator = currentResponses.GetEnumerator(); while (enumerator.MoveNext()) { Response current = enumerator.Current; if (current != null) { list.Add(Release1PlayerCopy.Normalize(current.label ?? string.Empty)); } } return list; } private static string? ReadMostRecentNpcMessageText(NPC contact) { //IL_0031: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Invalid comparison between Unknown and I4 MSGConversation val = ResolveConversation(contact); if (val == null) { return null; } List messageHistory = val.messageHistory; if (messageHistory == null) { return null; } string text = null; Enumerator enumerator = messageHistory.GetEnumerator(); while (enumerator.MoveNext()) { Message current = enumerator.Current; if (current != null && (int)current.sender == 1) { text = current.text; } } if (text != null) { return Release1PlayerCopy.Normalize(text); } return null; } private static MSGConversation? ResolveConversation(NPC npc) { GameObject gameObject = npc.gameObject; if ((Object)(object)gameObject == (Object)null) { return null; } NPC component = gameObject.GetComponent(); if (!((Object)(object)component == (Object)null)) { return component.MSGConversation; } return null; } private void LogOnce(string message) { if (_loggedMessages.Add(message)) { _log(message); } } } public sealed record Release1PhoneCallProjection(string CallerName, IReadOnlyList StageTexts) { public static Release1PhoneCallProjection Create(Release1PhoneCallRequest request) { ArgumentNullException.ThrowIfNull(request, "request"); request.Validate(); return new Release1PhoneCallProjection(request.CallerLabel, request.StageTexts.ToArray()); } } public sealed class S1ApiRelease1PhoneCallQueue : IRelease1PhoneCallQueue { private readonly Func _isAvailable; private readonly Action _queueCall; public S1ApiRelease1PhoneCallQueue(Func? isAvailable = null, Action? queueCall = null) { _isAvailable = isAvailable ?? new Func(IsNativeManagerAvailable); _queueCall = queueCall ?? new Action(QueueWithS1Api); } public bool IsAvailable(Release1PhoneCallRequest request) { ArgumentNullException.ThrowIfNull(request, "request"); return _isAvailable(); } public void Invoke(Release1PhoneCallRequest request) { ArgumentNullException.ThrowIfNull(request, "request"); _queueCall(Release1PhoneCallProjection.Create(request)); } private static void QueueWithS1Api(Release1PhoneCallProjection projection) { CallManager.QueueCall((PhoneCallDefinition)(object)new OcRelease1PhoneCallDefinition(projection.CallerName, projection.StageTexts.ToArray())); } private static bool IsNativeManagerAvailable() { try { return (Object)(object)Singleton.Instance != (Object)null; } catch { return false; } } } internal interface IRelease1SmallCourtesyCloseRegistration { bool TryRemove(); } internal interface IRelease1SmallCourtesyWorldAccess : IDisposable { Release1SmallCourtesyWorldReadStatus TryReadClock(out int elapsedDays, out int minuteOfDay); Release1SmallCourtesyWorldReadStatus TryReadProducts(out IReadOnlyList products); Release1SmallCourtesyWorldReadStatus TryReadDeadDrops(out IReadOnlyList drops); Release1SmallCourtesyWorldReadStatus TryReadPackaging(Release1SmallCourtesyPackageKind kind, out Release1SmallCourtesyPackagingCandidate packaging); Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlots(string deadDropGuid, out IReadOnlyList slots); Release1SmallCourtesyWorldReadStatus TryReadHoldRoom(out Release1HoldRoomSnapshot room); Release1SmallCourtesyWorldMutationStatus TrySetSlotLocked(string deadDropGuid, int slotIndex, bool locked); Release1SmallCourtesyWorldMutationStatus TryChangeSlotQuantity(string deadDropGuid, int slotIndex, int amount); Release1SmallCourtesyWorldMutationStatus TryInsertPackagedProduct(string deadDropGuid, int slotIndex, string productId, string packagingId, int quantity, out string reason); Release1SmallCourtesyWorldReadStatus TryAttachDropClosed(string deadDropGuid, Action callback, out IRelease1SmallCourtesyCloseRegistration? registration); Release1SmallCourtesyWorldReadStatus TryReadCashBalance(out float balance); Release1SmallCourtesyWorldMutationStatus TryChangeCashBalance(float amount); Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, out float balance); Release1SmallCourtesyWorldMutationStatus TryChangeDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, float amount); Release1SmallCourtesyWorldReadStatus TryReadHoldRoomSlotCashBalance(string closetGuid, int slotIndex, out float balance); Release1SmallCourtesyWorldMutationStatus TrySetHoldRoomSlotLocked(string closetGuid, int slotIndex, bool locked); Release1SmallCourtesyWorldMutationStatus TryChangeHoldRoomSlotCashBalance(string closetGuid, int slotIndex, float amount); Release1SmallCourtesyWorldReadStatus TryReadProductionActivity(out Release1ProductionActivitySnapshot activity); Release1SmallCourtesyWorldReadStatus TryReadFieldContact(string contactId, out Release1FieldContactSnapshot snapshot); Release1SmallCourtesyWorldMutationStatus TryParkFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldMutationStatus TryUnparkFieldContact(string contactId, float aheadMetres, out string reason); Release1SmallCourtesyWorldMutationStatus TryDespawnFieldContact(string contactId, out string reason); Release1SmallCourtesyWorldMutationStatus TryProvokeFieldContact(string contactId, out string reason); } public sealed class S1ApiRelease1SmallCourtesyWorld : IRelease1SmallCourtesyWorld, IDisposable { private delegate Release1SmallCourtesyWorldReadStatus ReadMany(out IReadOnlyList values); private sealed class CloseEntry { public string DeadDropGuid { get; } public Action Callback { get; } public Guid SessionEpoch { get; } public long LoadEpoch { get; } public string PlayerId { get; } public string ActiveSaveFolder { get; } public IRelease1SmallCourtesyCloseRegistration Registration { get; } public DropSubscription Token { get; } public bool Active { get; set; } private CloseEntry(string deadDropGuid, Action callback, Release1StoryHostContextSnapshot context, IRelease1SmallCourtesyCloseRegistration registration, S1ApiRelease1SmallCourtesyWorld owner, bool active) { DeadDropGuid = deadDropGuid; Callback = callback; SessionEpoch = context.SessionEpoch; LoadEpoch = context.LoadEpoch; PlayerId = context.PlayerId; ActiveSaveFolder = context.ActiveSaveFolder; Registration = registration; Active = active; Token = new DropSubscription(this, owner); } public static CloseEntry Attached(string guid, Action callback, Release1StoryHostContextSnapshot context, IRelease1SmallCourtesyCloseRegistration registration, S1ApiRelease1SmallCourtesyWorld owner) { return new CloseEntry(guid, callback, context, registration, owner, active: true); } public static CloseEntry Pending(string guid, Action callback, Release1StoryHostContextSnapshot context, IRelease1SmallCourtesyCloseRegistration registration, S1ApiRelease1SmallCourtesyWorld owner) { return new CloseEntry(guid, callback, context, registration, owner, active: false); } } private sealed class DropSubscription : IRelease1SmallCourtesyDropSubscription, IDisposable { private readonly CloseEntry _entry; private readonly S1ApiRelease1SmallCourtesyWorld _owner; public string DeadDropGuid => _entry.DeadDropGuid; public DropSubscription(CloseEntry entry, S1ApiRelease1SmallCourtesyWorld owner) { _entry = entry; _owner = owner; } public void Dispose() { _owner.Dispose(_entry); } } private readonly IRelease1StoryHostContext _hostContext; private readonly IRelease1SmallCourtesyWorldAccess _access; private readonly Harmony? _lockdownHarmony; private CloseEntry? _closeEntry; private bool _disposed; public const int MaximumSlotDecrement = 3; public const float MaximumWalletDebit = 25000f; public const float MaximumCashDecrement = 20000f; public const float MaximumFieldContactAheadMetres = 10f; public S1ApiRelease1SmallCourtesyWorld(IRelease1StoryHostContext hostContext, ISyndicateHqStorageRuntime? holdRoomStorage = null, IRelease1FieldContactRuntime? fieldContacts = null, Harmony? lockdownHarmony = null) : this(hostContext, new S1ApiRelease1SmallCourtesyWorldAccess(holdRoomStorage, fieldContacts), lockdownHarmony) { } internal S1ApiRelease1SmallCourtesyWorld(IRelease1StoryHostContext hostContext, IRelease1SmallCourtesyWorldAccess access, Harmony? lockdownHarmony = null) { _hostContext = hostContext ?? throw new ArgumentNullException("hostContext"); _access = access ?? throw new ArgumentNullException("access"); _lockdownHarmony = lockdownHarmony; } public Release1SmallCourtesyWorldReadStatus TryReadContext(out Release1StoryHostContextSnapshot context) { context = default(Release1StoryHostContextSnapshot); if (_disposed) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } try { Release1StoryHostContextReadStatus release1StoryHostContextReadStatus = _hostContext.TryRead(out context); if (release1StoryHostContextReadStatus == Release1StoryHostContextReadStatus.Ready) { return Release1SmallCourtesyWorldReadStatus.Ready; } context = default(Release1StoryHostContextSnapshot); return release1StoryHostContextReadStatus switch { Release1StoryHostContextReadStatus.Pending => Release1SmallCourtesyWorldReadStatus.Pending, Release1StoryHostContextReadStatus.NotAuthoritative => Release1SmallCourtesyWorldReadStatus.NotAuthoritative, Release1StoryHostContextReadStatus.UnsupportedMultiplayer => Release1SmallCourtesyWorldReadStatus.NotAuthoritative, Release1StoryHostContextReadStatus.AmbiguousIdentity => Release1SmallCourtesyWorldReadStatus.Faulted, _ => Release1SmallCourtesyWorldReadStatus.Faulted, }; } catch { context = default(Release1StoryHostContextSnapshot); return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldReadStatus TryReadCanonicalTotalMinutes(out double totalMinutes) { totalMinutes = 0.0; if (!HasAuthority()) { return AuthorityReadStatus(); } try { int elapsedDays; int minuteOfDay; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadClock(out elapsedDays, out minuteOfDay); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return release1SmallCourtesyWorldReadStatus; } bool flag = elapsedDays < 0; if (!flag) { bool flag2 = ((minuteOfDay < 0 || minuteOfDay >= 1440) ? true : false); flag = flag2; } if (flag) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } totalMinutes = (double)elapsedDays * 1440.0 + (double)minuteOfDay; return (!double.IsFinite(totalMinutes)) ? Release1SmallCourtesyWorldReadStatus.Unavailable : Release1SmallCourtesyWorldReadStatus.Ready; } catch { totalMinutes = 0.0; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldReadStatus TryReadProducts(out IReadOnlyList products) { return ReadValidated(_access.TryReadProducts, delegate(Release1SmallCourtesyProductCandidate candidate) { candidate.Validate(); }, out products); } public Release1SmallCourtesyWorldReadStatus TryReadDeadDrops(out IReadOnlyList drops) { return ReadValidated(_access.TryReadDeadDrops, delegate(Release1SmallCourtesyDropCandidate candidate) { candidate.Validate(); }, out drops); } public Release1SmallCourtesyWorldReadStatus TryReadPackaging(Release1SmallCourtesyPackageKind kind, out Release1SmallCourtesyPackagingCandidate packaging) { packaging = null; if (!Enum.IsDefined(kind) || !HasAuthority()) { return AuthorityReadStatus(); } try { Release1SmallCourtesyPackagingCandidate packaging2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadPackaging(kind, out packaging2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || (object)packaging2 == null) { return release1SmallCourtesyWorldReadStatus; } packaging2.Validate(); packaging = packaging2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { packaging = null; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlots(string deadDropGuid, out IReadOnlyList slots) { slots = Array.Empty(); if (!IsStableId(deadDropGuid)) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } return ReadValidated(delegate(out IReadOnlyList read) { return _access.TryReadDeadDropSlots(deadDropGuid, out read); }, delegate(Release1SmallCourtesySlotSnapshot candidate) { candidate.Validate(); }, out slots, requireUniqueIndex: true); } public Release1SmallCourtesyWorldReadStatus TryReadHoldRoom(out Release1HoldRoomSnapshot room) { room = Release1HoldRoomSnapshot.Unavailable(); if (!HasAuthority()) { return AuthorityReadStatus(); } try { Release1HoldRoomSnapshot room2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadHoldRoom(out room2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || (object)room2 == null) { return (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready) ? Release1SmallCourtesyWorldReadStatus.Unavailable : release1SmallCourtesyWorldReadStatus; } room2.Validate(); room = room2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { room = Release1HoldRoomSnapshot.Unavailable(); return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldReadStatus TryReadProductionActivity(out Release1ProductionActivitySnapshot activity) { activity = Release1ProductionActivitySnapshot.Empty; if (!HasAuthority()) { return AuthorityReadStatus(); } try { Release1ProductionActivitySnapshot activity2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadProductionActivity(out activity2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || (object)activity2 == null) { return (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready) ? Release1SmallCourtesyWorldReadStatus.Unavailable : release1SmallCourtesyWorldReadStatus; } activity2.Validate(); activity = activity2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { activity = Release1ProductionActivitySnapshot.Empty; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TrySetSlotLocked(string deadDropGuid, int slotIndex, bool locked) { if (!IsStableId(deadDropGuid) || slotIndex < 0 || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TrySetSlotLocked(deadDropGuid, slotIndex, locked); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryChangeSlotQuantity(string deadDropGuid, int slotIndex, int amount) { if (!IsStableId(deadDropGuid) || slotIndex < 0 || amount >= 0 || amount < -3 || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryChangeSlotQuantity(deadDropGuid, slotIndex, amount); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryInsertPackagedProduct(string deadDropGuid, int slotIndex, string productId, string packagingId, int quantity, out string reason) { reason = string.Empty; if (!IsStableId(deadDropGuid) || slotIndex < 0 || !IsStableId(productId) || !IsStableId(packagingId) || quantity <= 0 || !HasAuthority()) { reason = "the staging request was invalid or the world was not authoritative."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryInsertPackagedProduct(deadDropGuid, slotIndex, productId, packagingId, quantity, out reason); } catch (Exception ex) { reason = "inserting the packaged product threw an exception: " + ex.Message; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldReadStatus TrySubscribeDeadDropClosed(string deadDropGuid, Action callback, out IRelease1SmallCourtesyDropSubscription? subscription) { subscription = null; if (!IsStableId(deadDropGuid) || callback == null) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } if (TryReadContext(out var context) != Release1SmallCourtesyWorldReadStatus.Ready) { return AuthorityReadStatus(); } if (_closeEntry != null) { if (_closeEntry.Active && string.Equals(_closeEntry.DeadDropGuid, deadDropGuid, StringComparison.Ordinal) && _closeEntry.LoadEpoch == context.LoadEpoch && _closeEntry.Callback == callback) { subscription = _closeEntry.Token; return Release1SmallCourtesyWorldReadStatus.Ready; } _closeEntry.Active = false; if (!_closeEntry.Registration.TryRemove()) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } _closeEntry = null; } CloseEntry entry = null; Action callback2 = delegate { if (entry != null && _closeEntry == entry && entry.Active && TryReadContext(out var context2) == Release1SmallCourtesyWorldReadStatus.Ready && !(context2.SessionEpoch != entry.SessionEpoch) && context2.LoadEpoch == entry.LoadEpoch && string.Equals(context2.PlayerId, entry.PlayerId, StringComparison.Ordinal) && string.Equals(context2.ActiveSaveFolder, entry.ActiveSaveFolder, StringComparison.OrdinalIgnoreCase)) { entry.Callback(entry.DeadDropGuid); } }; try { IRelease1SmallCourtesyCloseRegistration registration; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryAttachDropClosed(deadDropGuid, callback2, out registration); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || registration == null) { if (registration != null && !registration.TryRemove()) { entry = CloseEntry.Pending(deadDropGuid, callback, context, registration, this); _closeEntry = entry; } return (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready) ? Release1SmallCourtesyWorldReadStatus.Faulted : release1SmallCourtesyWorldReadStatus; } entry = CloseEntry.Attached(deadDropGuid, callback, context, registration, this); _closeEntry = entry; subscription = entry.Token; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldReadStatus TryReadCashBalance(out float balance) { balance = 0f; if (!HasAuthority()) { return AuthorityReadStatus(); } try { float balance2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadCashBalance(out balance2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || !float.IsFinite(balance2) || balance2 < 0f) { return (release1SmallCourtesyWorldReadStatus == Release1SmallCourtesyWorldReadStatus.Ready) ? Release1SmallCourtesyWorldReadStatus.Unavailable : release1SmallCourtesyWorldReadStatus; } balance = balance2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { balance = 0f; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TryChangeCashBalance(float amount) { if (!float.IsFinite(amount) || amount <= 0f || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryChangeCashBalance(amount); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryDebitCashBalance(float amount) { if (!float.IsFinite(amount) || amount >= 0f || amount < -25000f || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { if (_access.TryReadCashBalance(out var balance) != Release1SmallCourtesyWorldReadStatus.Ready || !float.IsFinite(balance) || balance < 0f) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } if (balance + amount < 0f) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } return _access.TryChangeCashBalance(amount); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryEngageLockdown(out string reason) { reason = string.Empty; if (_lockdownHarmony == null) { reason = "no Harmony instance was supplied to the lockdown seam."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } try { return Map(Release1LockdownGatePatch.TryEngage(_lockdownHarmony, delegate { }, HasAuthority), out reason); } catch (Exception ex) { reason = "the lockdown engage threw: " + ex.GetType().Name; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryReleaseLockdown(out string reason) { reason = string.Empty; try { return Map(Release1LockdownGatePatch.TryRelease(HasAuthority), out reason); } catch (Exception ex) { reason = "the lockdown release threw: " + ex.GetType().Name; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } private static Release1SmallCourtesyWorldMutationStatus Map(Release1StagingHarnessResult result, out string reason) { reason = string.Join(" ", result.Lines); switch (result.Status) { case Release1StagingHarnessStatus.Succeeded: return Release1SmallCourtesyWorldMutationStatus.Succeeded; case Release1StagingHarnessStatus.Unavailable: case Release1StagingHarnessStatus.NeedsOptionB: return Release1SmallCourtesyWorldMutationStatus.Unavailable; case Release1StagingHarnessStatus.Ambiguous: return Release1SmallCourtesyWorldMutationStatus.Ambiguous; default: return Release1SmallCourtesyWorldMutationStatus.Rejected; } } public Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, out float balance) { balance = 0f; if (!IsStableId(deadDropGuid) || slotIndex < 0) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } if (!HasAuthority()) { return AuthorityReadStatus(); } try { float balance2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadDeadDropSlotCashBalance(deadDropGuid, slotIndex, out balance2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return release1SmallCourtesyWorldReadStatus; } if (!float.IsFinite(balance2) || balance2 <= 0f) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } balance = balance2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { balance = 0f; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TryChangeDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, float amount) { if (!IsStableId(deadDropGuid) || slotIndex < 0 || !float.IsFinite(amount) || amount >= 0f || amount < -20000f || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryChangeDeadDropSlotCashBalance(deadDropGuid, slotIndex, amount); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldReadStatus TryReadHoldRoomSlotCashBalance(string closetGuid, int slotIndex, out float balance) { balance = 0f; if (!IsStableId(closetGuid) || slotIndex < 0) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } if (!HasAuthority()) { return AuthorityReadStatus(); } try { float balance2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadHoldRoomSlotCashBalance(closetGuid, slotIndex, out balance2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return release1SmallCourtesyWorldReadStatus; } if (!float.IsFinite(balance2) || balance2 <= 0f) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } balance = balance2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { balance = 0f; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TrySetHoldRoomSlotLocked(string closetGuid, int slotIndex, bool locked) { if (!IsStableId(closetGuid) || slotIndex < 0 || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TrySetHoldRoomSlotLocked(closetGuid, slotIndex, locked); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryChangeHoldRoomSlotCashBalance(string closetGuid, int slotIndex, float amount) { if (!IsStableId(closetGuid) || slotIndex < 0 || !float.IsFinite(amount) || amount >= 0f || amount < -20000f || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryChangeHoldRoomSlotCashBalance(closetGuid, slotIndex, amount); } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldReadStatus TryReadFieldContact(string contactId, out Release1FieldContactSnapshot snapshot) { snapshot = Release1FieldContactSnapshot.Unavailable(); if (!IsStableId(contactId)) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } if (!HasAuthority()) { return AuthorityReadStatus(); } try { Release1FieldContactSnapshot snapshot2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _access.TryReadFieldContact(contactId, out snapshot2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || (object)snapshot2 == null) { return release1SmallCourtesyWorldReadStatus; } snapshot2.Validate(); snapshot = snapshot2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { snapshot = Release1FieldContactSnapshot.Unavailable(); return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TryParkFieldContact(string contactId, out string reason) { reason = "the field contact park was refused."; if (!IsStableId(contactId) || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryParkFieldContact(contactId, out reason); } catch { reason = "the field contact park threw."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryUnparkFieldContact(string contactId, float aheadMetres, out string reason) { reason = "the field contact unpark was refused."; if (!IsStableId(contactId) || !float.IsFinite(aheadMetres) || aheadMetres <= 0f || aheadMetres > 10f || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryUnparkFieldContact(contactId, aheadMetres, out reason); } catch { reason = "the field contact unpark threw."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryDespawnFieldContact(string contactId, out string reason) { reason = "the field contact despawn was refused."; if (!IsStableId(contactId) || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryDespawnFieldContact(contactId, out reason); } catch { reason = "the field contact despawn threw."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryProvokeFieldContact(string contactId, out string reason) { reason = "the field contact provoke was refused."; if (!IsStableId(contactId) || !HasAuthority()) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { return _access.TryProvokeFieldContact(contactId, out reason); } catch { reason = "the field contact provoke threw."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public void Dispose() { if (_disposed) { return; } _disposed = true; if (_closeEntry != null) { _closeEntry.Active = false; if (_closeEntry.Registration.TryRemove()) { _closeEntry = null; } } _access.Dispose(); } private void Dispose(CloseEntry entry) { if (_closeEntry == entry) { entry.Active = false; if (entry.Registration.TryRemove()) { _closeEntry = null; } } } private bool HasAuthority() { Release1StoryHostContextSnapshot context; return TryReadContext(out context) == Release1SmallCourtesyWorldReadStatus.Ready; } private Release1SmallCourtesyWorldReadStatus AuthorityReadStatus() { Release1StoryHostContextSnapshot context; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = TryReadContext(out context); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { return release1SmallCourtesyWorldReadStatus; } return Release1SmallCourtesyWorldReadStatus.Unavailable; } private Release1SmallCourtesyWorldReadStatus ReadValidated(ReadMany read, Action validate, out IReadOnlyList values, bool requireUniqueIndex = false) { values = Array.Empty(); if (!HasAuthority()) { return AuthorityReadStatus(); } try { IReadOnlyList values2; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = read(out values2); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready || values2 == null) { return release1SmallCourtesyWorldReadStatus; } T[] array = values2.ToArray(); T[] array2 = array; foreach (T val in array2) { if (val == null) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } validate(val); } if (requireUniqueIndex && (from Release1SmallCourtesySlotSnapshot value in array select value.SlotIndex).Distinct().Count() != array.Length) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } values = array; return Release1SmallCourtesyWorldReadStatus.Ready; } catch { values = Array.Empty(); return Release1SmallCourtesyWorldReadStatus.Faulted; } } private static bool IsStableId(string? value) { try { Release1SmallCourtesyAssignment.ValidateStableId(value, "value"); return true; } catch { return false; } } } internal sealed class S1ApiRelease1SmallCourtesyWorldAccess : IRelease1SmallCourtesyWorldAccess, IDisposable { private sealed class S1ApiCloseRegistration : IRelease1SmallCourtesyCloseRegistration { private readonly DeadDropInstance _drop; private readonly StorageInstance _storage; private readonly Action _callback; private bool _maybeAttached = true; public S1ApiCloseRegistration(DeadDropInstance drop, StorageInstance storage, Action callback) { _drop = drop; _storage = storage; _callback = callback; } public void MarkAttached() { _maybeAttached = true; } public bool TryRemove() { if (!_maybeAttached) { return true; } try { _storage.OnClosed -= _callback; _maybeAttached = false; return true; } catch { return false; } } } private const string UnpackagedId = "unpackaged"; private const float CashBalanceTolerance = 0.01f; private readonly HashSet<(string Guid, int SlotIndex)> _ownedSlotLocks = new HashSet<(string, int)>(); private readonly ISyndicateHqStorageRuntime? _holdRoomStorage; private readonly IRelease1FieldContactRuntime? _fieldContacts; private const string NoFieldContactRuntime = "no field contact runtime is composed."; public S1ApiRelease1SmallCourtesyWorldAccess() : this(null, null) { } public S1ApiRelease1SmallCourtesyWorldAccess(ISyndicateHqStorageRuntime? holdRoomStorage) : this(holdRoomStorage, null) { } public S1ApiRelease1SmallCourtesyWorldAccess(ISyndicateHqStorageRuntime? holdRoomStorage, IRelease1FieldContactRuntime? fieldContacts) { _holdRoomStorage = holdRoomStorage; _fieldContacts = fieldContacts; } public Release1SmallCourtesyWorldReadStatus TryReadFieldContact(string contactId, out Release1FieldContactSnapshot snapshot) { if (_fieldContacts != null) { return _fieldContacts.TryReadFieldContact(contactId, out snapshot); } snapshot = Release1FieldContactSnapshot.Unavailable(); return Release1SmallCourtesyWorldReadStatus.Unavailable; } public Release1SmallCourtesyWorldMutationStatus TryParkFieldContact(string contactId, out string reason) { if (_fieldContacts != null) { return _fieldContacts.TryParkFieldContact(contactId, out reason); } reason = "no field contact runtime is composed."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } public Release1SmallCourtesyWorldMutationStatus TryUnparkFieldContact(string contactId, float aheadMetres, out string reason) { if (_fieldContacts != null) { return _fieldContacts.TryUnparkFieldContact(contactId, aheadMetres, out reason); } reason = "no field contact runtime is composed."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } public Release1SmallCourtesyWorldMutationStatus TryDespawnFieldContact(string contactId, out string reason) { if (_fieldContacts != null) { return _fieldContacts.TryDespawnFieldContact(contactId, out reason); } reason = "no field contact runtime is composed."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } public Release1SmallCourtesyWorldMutationStatus TryProvokeFieldContact(string contactId, out string reason) { if (_fieldContacts != null) { return _fieldContacts.TryProvokeFieldContact(contactId, out reason); } reason = "no field contact runtime is composed."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } public Release1SmallCourtesyWorldReadStatus TryReadHoldRoom(out Release1HoldRoomSnapshot room) { if (_holdRoomStorage == null) { room = Release1HoldRoomSnapshot.Unavailable(); return Release1SmallCourtesyWorldReadStatus.Unavailable; } room = _holdRoomStorage.ReadHoldRoom(); return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldReadStatus TryReadHoldRoomSlotCashBalance(string closetGuid, int slotIndex, out float balance) { if (_holdRoomStorage == null) { balance = 0f; return Release1SmallCourtesyWorldReadStatus.Unavailable; } return _holdRoomStorage.TryReadClosetSlotCashBalance(closetGuid, slotIndex, out balance); } public Release1SmallCourtesyWorldMutationStatus TrySetHoldRoomSlotLocked(string closetGuid, int slotIndex, bool locked) { if (_holdRoomStorage == null) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } return _holdRoomStorage.TrySetClosetSlotLocked(closetGuid, slotIndex, locked); } public Release1SmallCourtesyWorldMutationStatus TryChangeHoldRoomSlotCashBalance(string closetGuid, int slotIndex, float amount) { if (_holdRoomStorage == null) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } return _holdRoomStorage.TryChangeClosetSlotCashBalance(closetGuid, slotIndex, amount); } public Release1SmallCourtesyWorldReadStatus TryReadProductionActivity(out Release1ProductionActivitySnapshot activity) { activity = Release1ProductionActivitySnapshot.Empty; List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); HashSet seenStations = new HashSet(StringComparer.Ordinal); foreach (Property item in OwnedProperties()) { if (!((Object)(object)item == (Object)null)) { string propertyCode = item.PropertyCode; if (!string.IsNullOrWhiteSpace(propertyCode) && hashSet.Add(propertyCode)) { string propertyName = item.PropertyName; list.Add(new Release1ProductionPropertySnapshot(propertyCode, string.IsNullOrWhiteSpace(propertyName) ? propertyCode : propertyName, ReadEmployees(item), ReadStations(item, seenStations))); } } } activity = new Release1ProductionActivitySnapshot(list); return Release1SmallCourtesyWorldReadStatus.Ready; } private static IEnumerable OwnedProperties() { List ownedProperties = Property.OwnedProperties; if (ownedProperties != null) { Enumerator enumerator = ownedProperties.GetEnumerator(); while (enumerator.MoveNext()) { yield return enumerator.Current; } } List ownedBusinesses = Business.OwnedBusinesses; if (ownedBusinesses != null) { Enumerator enumerator2 = ownedBusinesses.GetEnumerator(); while (enumerator2.MoveNext()) { yield return (Property)(object)enumerator2.Current; } } } private static IReadOnlyList ReadEmployees(Property property) { List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); List employees = property.Employees; if (employees == null) { return list; } Enumerator enumerator = employees.GetEnumerator(); while (enumerator.MoveNext()) { Employee current = enumerator.Current; if (!((Object)(object)current == (Object)null)) { string iD = ((NPC)current).ID; if (!string.IsNullOrWhiteSpace(iD) && hashSet.Add(iD)) { list.Add(ReadEmployee(current, iD)); } } } return list; } private static Release1ProductionEmployeeSnapshot ReadEmployee(Employee employee, string id) { //IL_004f: 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) NPCBehaviour behaviour = ((NPC)employee).Behaviour; Behaviour val = (((Object)(object)behaviour == (Object)null) ? null : behaviour.activeBehaviour); if ((Object)(object)val == (Object)null) { val = null; } string text = (((NPC)employee).FirstName + " " + ((NPC)employee).LastName).Trim(); return new Release1ProductionEmployeeSnapshot(id, string.IsNullOrWhiteSpace(text) ? id : text, ((object)employee.EmployeeType/*cast due to .constrained prefix*/).ToString(), employee.Fired, employee.PaidForToday, employee.IsWaitingOutside, Math.Max(0, employee.TicksSinceLastWork), employee.IsAnyWorkInProgress(), (val == null) ? null : val.Name, (val == null) ? null : ((MemberInfo)((Object)val).GetIl2CppType()).Name, val != null && val.Active); } private static IReadOnlyList ReadStations(Property property, HashSet seenStations) { //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) List list = new List(); List buildableItems = property.BuildableItems; if (buildableItems == null) { return list; } Enumerator enumerator = buildableItems.GetEnumerator(); while (enumerator.MoveNext()) { BuildableItem current = enumerator.Current; if ((Object)(object)current == (Object)null || current.IsDestroyed) { continue; } string text = ((object)current.GUID/*cast due to .constrained prefix*/).ToString(); if (!string.IsNullOrWhiteSpace(text) && seenStations.Add(text)) { Release1ProductionStationSnapshot release1ProductionStationSnapshot = ReadStation(current, text); if ((object)release1ProductionStationSnapshot == null) { seenStations.Remove(text); } else { list.Add(release1ProductionStationSnapshot); } } } return list; } private static Release1ProductionStationSnapshot? ReadStation(BuildableItem item, string guid) { ChemistryStation val = ((Il2CppObjectBase)item).TryCast(); if ((Object)(object)val != (Object)null) { ChemistryCookOperation currentCookOperation = val.CurrentCookOperation; if (currentCookOperation != null) { return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.ChemistryStation, Running: true, Identity(currentCookOperation.RecipeID), Math.Max(0, currentCookOperation.CurrentTime)); } return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.ChemistryStation, Running: false, null, 0); } LabOven val2 = ((Il2CppObjectBase)item).TryCast(); if ((Object)(object)val2 != (Object)null) { OvenCookOperation currentOperation = val2.CurrentOperation; if (currentOperation != null) { return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.LabOven, Running: true, Identity(currentOperation.ProductID), Math.Max(0, currentOperation.CookProgress)); } return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.LabOven, Running: false, null, 0); } MixingStation val3 = ((Il2CppObjectBase)item).TryCast(); if ((Object)(object)val3 != (Object)null) { MixOperation currentMixOperation = val3.CurrentMixOperation; if (currentMixOperation != null) { return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.MixingStation, Running: true, Identity(currentMixOperation.ProductID), Math.Max(0, val3.CurrentMixTime)); } return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.MixingStation, Running: false, null, 0); } Cauldron val4 = ((Il2CppObjectBase)item).TryCast(); if ((Object)(object)val4 != (Object)null) { if (!val4.isCooking) { return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.Cauldron, Running: false, null, 0); } int progress = Release1ProductionStationProgress.CauldronProgress(val4.CookTime, val4.RemainingCookTime, remainingCountsDown: true); return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.Cauldron, Running: true, "cauldron-cook", progress, val4.CookTime, val4.RemainingCookTime); } DryingRack val5 = ((Il2CppObjectBase)item).TryCast(); if ((Object)(object)val5 != (Object)null) { List dryingOperations = val5.DryingOperations; if (dryingOperations == null || dryingOperations.Count == 0) { return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.DryingRack, Running: false, null, 0); } SortedSet sortedSet = new SortedSet(StringComparer.Ordinal); Enumerator enumerator = dryingOperations.GetEnumerator(); while (enumerator.MoveNext()) { DryingOperation current = enumerator.Current; if (current != null && !string.IsNullOrWhiteSpace(current.ItemID)) { sortedSet.Add(current.ItemID); } } if (sortedSet.Count == 0) { return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.DryingRack, Running: false, null, 0); } return new Release1ProductionStationSnapshot(guid, Release1ProductionStationKind.DryingRack, Running: true, string.Join("|", sortedSet), 0); } return null; } private static string Identity(string? value) { if (!string.IsNullOrWhiteSpace(value)) { return value; } return "unnamed"; } public Release1SmallCourtesyWorldReadStatus TryReadClock(out int elapsedDays, out int minuteOfDay) { elapsedDays = TimeManager.ElapsedDays; minuteOfDay = TimeManager.GetMinutesFrom24HourTime(TimeManager.CurrentTime); return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldReadStatus TryReadProducts(out IReadOnlyList products) { List list = new List(); ProductDefinition[] array = ProductManager.DiscoveredProducts ?? Array.Empty(); foreach (ProductDefinition val in array) { if (val != null) { try { Release1SmallCourtesyProductCandidate release1SmallCourtesyProductCandidate = new Release1SmallCourtesyProductCandidate(((ItemDefinition)val).ID, ((ItemDefinition)val).Name, val.Price, IsDiscovered: true); release1SmallCourtesyProductCandidate.Validate(); list.Add(release1SmallCourtesyProductCandidate); } catch { } } } products = list; return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldReadStatus TryReadDeadDrops(out IReadOnlyList drops) { //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_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) //IL_00bc: 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_0136: 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) List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); DeadDropInstance[] array = DeadDropManager.All ?? Array.Empty(); foreach (DeadDropInstance val in array) { if (val == null) { continue; } try { string gUID = val.GUID; if (!TryResolveNativeDrop(gUID, out DeadDrop drop, out bool ambiguous)) { if (ambiguous) { drops = Array.Empty(); return Release1SmallCourtesyWorldReadStatus.Unavailable; } } else { if ((Object)(object)drop == (Object)null) { continue; } Scene scene = ((Component)drop).gameObject.scene; if (!((Scene)(ref scene)).IsValid()) { continue; } if (string.IsNullOrWhiteSpace(drop.DeadDropName) || string.IsNullOrWhiteSpace(drop.DeadDropDescription)) { drops = Array.Empty(); return Release1SmallCourtesyWorldReadStatus.Unavailable; } Vector3 position = val.Position; if (!IsFinite(position)) { continue; } StorageInstance storage = val.Storage; if (storage != null) { bool isEmpty = storage.Slots?.All((ItemSlotInstance slot) => slot != null && slot.Quantity == 0) ?? false; Release1SmallCourtesyDropCandidate release1SmallCourtesyDropCandidate = new Release1SmallCourtesyDropCandidate(gUID, drop.DeadDropName.Trim(), drop.DeadDropDescription.Trim(), position.x, position.y, position.z, isEmpty); release1SmallCourtesyDropCandidate.Validate(); if (!hashSet.Add(gUID)) { drops = Array.Empty(); return Release1SmallCourtesyWorldReadStatus.Unavailable; } list.Add(release1SmallCourtesyDropCandidate); } continue; } } catch { } } drops = list; return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldReadStatus TryReadPackaging(Release1SmallCourtesyPackageKind kind, out Release1SmallCourtesyPackagingCandidate packaging) { packaging = null; string text = ((kind == Release1SmallCourtesyPackageKind.Brick) ? "brick" : "jar"); ItemDefinition itemDefinition = ItemManager.GetItemDefinition(text); if (itemDefinition == null || !string.Equals(itemDefinition.ID, text, StringComparison.Ordinal)) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } packaging = new Release1SmallCourtesyPackagingCandidate(itemDefinition.ID, itemDefinition.Name); packaging.Validate(); return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlots(string deadDropGuid, out IReadOnlyList slots) { //IL_003a: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) slots = Array.Empty(); DeadDropInstance wrapper; bool ambiguous; bool num = TryResolveApiDrop(deadDropGuid, out wrapper, out ambiguous); DeadDrop drop; bool ambiguous2; bool flag = TryResolveNativeDrop(deadDropGuid, out drop, out ambiguous2); if (!num || !flag) { if (!(ambiguous || ambiguous2)) { return Release1SmallCourtesyWorldReadStatus.Pending; } return Release1SmallCourtesyWorldReadStatus.Unavailable; } if (!((Object)(object)drop == (Object)null)) { Scene scene = ((Component)drop).gameObject.scene; if (((Scene)(ref scene)).IsValid() && !((Object)(object)drop.Storage == (Object)null)) { StorageInstance storage = wrapper.Storage; if (storage == null) { return Release1SmallCourtesyWorldReadStatus.Pending; } ItemSlotInstance[] slots2 = storage.Slots; List itemSlots = ((StorageEntity)drop.Storage).ItemSlots; if (slots2 == null || itemSlots == null || slots2.Length != itemSlots.Count) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } List list = new List(slots2.Length); for (int i = 0; i < slots2.Length; i++) { ItemSlotInstance val = slots2[i]; if (val == null) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } ItemInstance itemInstance = val.ItemInstance; if (val.Quantity == 0 || itemInstance == null) { list.Add(new Release1SmallCourtesySlotSnapshot(i, null, null, 0, IsPackaged: false, 0f)); continue; } string iD = itemInstance.Definition.ID; string packagingId = "unpackaged"; bool flag2 = false; float monetaryValue = 0f; ProductInstance val2 = (ProductInstance)(object)((itemInstance is ProductInstance) ? itemInstance : null); if (val2 != null) { flag2 = val2.IsPackaged; if (flag2) { packagingId = ((ItemDefinition)val2.AppliedPackaging).ID; } ItemInstance itemInstance2 = itemSlots[i].ItemInstance; ProductItemInstance val3 = ((itemInstance2 == null) ? null : ((Il2CppObjectBase)itemInstance2).TryCast()); if (val3 == null) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } monetaryValue = ((BaseItemInstance)val3).GetMonetaryValue(); } Release1SmallCourtesySlotSnapshot release1SmallCourtesySlotSnapshot = new Release1SmallCourtesySlotSnapshot(i, iD, packagingId, val.Quantity, flag2, monetaryValue); release1SmallCourtesySlotSnapshot.Validate(); list.Add(release1SmallCourtesySlotSnapshot); } slots = list; return Release1SmallCourtesyWorldReadStatus.Ready; } } return Release1SmallCourtesyWorldReadStatus.Pending; } public Release1SmallCourtesyWorldMutationStatus TrySetSlotLocked(string deadDropGuid, int slotIndex, bool locked) { if (!TryResolveNativeSlot(deadDropGuid, slotIndex, out ItemSlot slot)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } (string, int) item = (deadDropGuid, slotIndex); if (locked && !_ownedSlotLocks.Contains(item) && (slot.IsRemovalLocked || slot.IsAddLocked)) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!locked && !_ownedSlotLocks.Contains(item)) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (locked) { _ownedSlotLocks.Add(item); } try { slot.SetIsRemovalLocked(locked); slot.SetIsAddLocked(locked); if (slot.IsRemovalLocked != locked || slot.IsAddLocked != locked) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } if (!locked) { _ownedSlotLocks.Remove(item); } return Release1SmallCourtesyWorldMutationStatus.Succeeded; } catch { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryChangeSlotQuantity(string deadDropGuid, int slotIndex, int amount) { (string, int) item = (deadDropGuid, slotIndex); if (!_ownedSlotLocks.Contains(item)) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!TryResolveNativeSlot(deadDropGuid, slotIndex, out ItemSlot slot)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } int quantity = slot.Quantity; if (amount >= 0 || quantity + amount < 0) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { slot.SetIsRemovalLocked(false); if (slot.IsRemovalLocked) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } slot.ChangeQuantity(amount, true); } finally { slot.SetIsRemovalLocked(true); } if (!slot.IsRemovalLocked) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } if (slot.Quantity != quantity + amount) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } return Release1SmallCourtesyWorldMutationStatus.Succeeded; } public Release1SmallCourtesyWorldMutationStatus TryInsertPackagedProduct(string deadDropGuid, int slotIndex, string productId, string packagingId, int quantity, out string reason) { reason = string.Empty; if (!TryResolveNativeSlot(deadDropGuid, slotIndex, out ItemSlot slot)) { reason = "the dead drop slot could not be resolved."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } if (slot.Quantity != 0) { reason = "the slot was not empty before staging."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!TryResolveNativeDrop(deadDropGuid, out DeadDrop drop, out bool _) || (Object)(object)drop.Storage == (Object)null) { reason = "the dead drop storage could not be resolved."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } WorldStorageEntity storage = drop.Storage; ItemDefinition item = Registry.GetItem(productId); ProductDefinition val = (((Object)(object)item == (Object)null) ? null : ((Il2CppObjectBase)item).TryCast()); if ((Object)(object)val == (Object)null) { reason = "the product definition could not be resolved from the registry."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } ItemDefinition item2 = Registry.GetItem(packagingId); PackagingDefinition val2 = (((Object)(object)item2 == (Object)null) ? null : ((Il2CppObjectBase)item2).TryCast()); if ((Object)(object)val2 == (Object)null) { reason = "the packaging definition could not be resolved from the registry."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } ItemInstance defaultInstance = ((ItemDefinition)val).GetDefaultInstance(quantity); ProductItemInstance val3 = ((defaultInstance == null) ? null : ((Il2CppObjectBase)defaultInstance).TryCast()); if (val3 == null) { reason = "the packaged product instance could not be created."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { val3.SetPackaging(val2); if (!((StorageEntity)storage).CanItemFit((ItemInstance)(object)val3, 1)) { reason = "the storage refused the item."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } ((StorageEntity)storage).InsertItem((ItemInstance)(object)val3, true); } catch (Exception ex) { reason = "inserting the packaged product threw an exception: " + ex.Message; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } IReadOnlyList slots; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = TryReadDeadDropSlots(deadDropGuid, out slots); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { reason = $"the slot could not be read back after staging, status was {release1SmallCourtesyWorldReadStatus}."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } Release1SmallCourtesySlotSnapshot[] array = slots.Where((Release1SmallCourtesySlotSnapshot candidate) => string.Equals(candidate.ProductId, productId, StringComparison.Ordinal) && string.Equals(candidate.PackagingId, packagingId, StringComparison.Ordinal) && candidate.Quantity == quantity).ToArray(); if (array.Length != 1) { reason = $"expected exactly one slot to hold the staged item after insertion, found {array.Length}."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } reason = $"the packaged product was staged and read back with the exact expected values in slot {array[0].SlotIndex}."; return Release1SmallCourtesyWorldMutationStatus.Succeeded; } public Release1SmallCourtesyWorldReadStatus TryAttachDropClosed(string deadDropGuid, Action callback, out IRelease1SmallCourtesyCloseRegistration? registration) { registration = null; if (!TryResolveApiDrop(deadDropGuid, out DeadDropInstance wrapper, out bool ambiguous)) { if (!ambiguous) { return Release1SmallCourtesyWorldReadStatus.Pending; } return Release1SmallCourtesyWorldReadStatus.Unavailable; } StorageInstance storage = wrapper.Storage; if (storage == null) { return Release1SmallCourtesyWorldReadStatus.Pending; } S1ApiCloseRegistration s1ApiCloseRegistration = (S1ApiCloseRegistration)(registration = new S1ApiCloseRegistration(wrapper, storage, callback)); try { storage.OnClosed += callback; s1ApiCloseRegistration.MarkAttached(); return Release1SmallCourtesyWorldReadStatus.Ready; } catch { return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldReadStatus TryReadCashBalance(out float balance) { balance = Money.GetCashBalance(); return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldMutationStatus TryChangeCashBalance(float amount) { Money.ChangeCashBalance(amount, true, false); return Release1SmallCourtesyWorldMutationStatus.Succeeded; } public Release1SmallCourtesyWorldReadStatus TryReadDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, out float balance) { balance = 0f; if (!TryResolveNativeSlot(deadDropGuid, slotIndex, out ItemSlot slot)) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } if (slot.Quantity == 0) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } ItemInstance itemInstance = slot.ItemInstance; CashInstance val = ((itemInstance == null) ? null : ((Il2CppObjectBase)itemInstance).TryCast()); if (val == null) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } balance = val.Balance; return Release1SmallCourtesyWorldReadStatus.Ready; } public Release1SmallCourtesyWorldMutationStatus TryChangeDeadDropSlotCashBalance(string deadDropGuid, int slotIndex, float amount) { (string, int) item = (deadDropGuid, slotIndex); if (!_ownedSlotLocks.Contains(item)) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!TryResolveNativeSlot(deadDropGuid, slotIndex, out ItemSlot slot)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } ItemInstance itemInstance = slot.ItemInstance; CashInstance val = ((itemInstance == null) ? null : ((Il2CppObjectBase)itemInstance).TryCast()); if (val == null) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } float balance = val.Balance; if (amount >= 0f || balance + amount < 0f) { return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { slot.SetIsRemovalLocked(false); if (slot.IsRemovalLocked) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } val.ChangeBalance(amount); } finally { slot.SetIsRemovalLocked(true); } if (!slot.IsRemovalLocked) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } float num = balance + amount; ItemInstance itemInstance2 = slot.ItemInstance; if (num <= 0f) { if (itemInstance2 == null || slot.Quantity == 0) { return Release1SmallCourtesyWorldMutationStatus.Succeeded; } CashInstance val2 = ((Il2CppObjectBase)itemInstance2).TryCast(); if (val2 == null || !(MathF.Abs(val2.Balance) <= 0.01f)) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } return Release1SmallCourtesyWorldMutationStatus.Succeeded; } CashInstance val3 = ((itemInstance2 == null) ? null : ((Il2CppObjectBase)itemInstance2).TryCast()); if (val3 == null || !(MathF.Abs(val3.Balance - num) <= 0.01f)) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } return Release1SmallCourtesyWorldMutationStatus.Succeeded; } public void Dispose() { (string, int)[] array = _ownedSlotLocks.ToArray(); for (int i = 0; i < array.Length; i++) { var (text, num) = array[i]; if (TryResolveNativeSlot(text, num, out ItemSlot slot)) { try { slot.SetIsRemovalLocked(false); slot.SetIsAddLocked(false); } catch { continue; } } _ownedSlotLocks.Remove((text, num)); } } private static bool TryResolveApiDrop(string guid, out DeadDropInstance wrapper, out bool ambiguous) { wrapper = null; ambiguous = false; DeadDropInstance[] array = (DeadDropManager.All ?? Array.Empty()).Where((DeadDropInstance candidate) => candidate != null && string.Equals(candidate.GUID, guid, StringComparison.Ordinal)).Take(2).ToArray(); if (array.Length == 1) { wrapper = array[0]; return true; } ambiguous = array.Length > 1; return false; } private static bool TryResolveNativeDrop(string guid, out DeadDrop drop, out bool ambiguous) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0044: 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) drop = null; ambiguous = false; int num = 0; List deadDrops = DeadDrop.DeadDrops; if (deadDrops == null) { return false; } Enumerator enumerator = deadDrops.GetEnumerator(); while (enumerator.MoveNext()) { DeadDrop current = enumerator.Current; if ((Object)(object)current == (Object)null) { continue; } try { Scene scene = ((Component)current).gameObject.scene; if (((Scene)(ref scene)).IsValid() && string.Equals(((object)current.GUID/*cast due to .constrained prefix*/).ToString(), guid, StringComparison.Ordinal)) { num++; if (num == 1) { drop = current; } if (num > 1) { ambiguous = true; drop = null; return false; } } } catch { } } return num == 1; } private static bool TryResolveNativeSlot(string guid, int slotIndex, out ItemSlot slot) { slot = null; if (!TryResolveNativeDrop(guid, out DeadDrop drop, out bool _) || (Object)(object)drop.Storage == (Object)null || ((StorageEntity)drop.Storage).ItemSlots == null || slotIndex < 0 || slotIndex >= ((StorageEntity)drop.Storage).ItemSlots.Count) { return false; } slot = ((StorageEntity)drop.Storage).ItemSlots[slotIndex]; return slot != null; } private static bool IsFinite(Vector3 value) { //IL_0000: 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_001a: Unknown result type (might be due to invalid IL or missing references) if (float.IsFinite(value.x) && float.IsFinite(value.y)) { return float.IsFinite(value.z); } return false; } } public sealed class S1CartelStatusSource : IRelease1CartelStatusSource { public Release1CartelStatusReadStatus TryRead(out Release1CartelStatus status) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) status = Release1CartelStatus.Unknown; try { if (!NetworkSingleton.InstanceExists) { return Release1CartelStatusReadStatus.Pending; } Cartel instance = NetworkSingleton.Instance; if ((Object)(object)instance == (Object)null) { return Release1CartelStatusReadStatus.Pending; } return (!TryMap(instance.Status, out status)) ? Release1CartelStatusReadStatus.UnsupportedValue : Release1CartelStatusReadStatus.Ready; } catch { status = Release1CartelStatus.Unknown; return Release1CartelStatusReadStatus.Faulted; } } internal static bool TryMap(ECartelStatus nativeStatus, out Release1CartelStatus status) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected I4, but got Unknown //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Invalid comparison between Unknown and I4 status = (int)nativeStatus switch { 0 => Release1CartelStatus.Unknown, 2 => Release1CartelStatus.Hostile, 1 => Release1CartelStatus.Truced, 3 => Release1CartelStatus.Defeated, _ => Release1CartelStatus.Unknown, }; if ((int)nativeStatus <= 3) { return true; } return false; } } public interface ISyndicateHqListenerRegistration { string StableIdentity { get; } bool TryRemove(out string? failure); } public interface ISyndicateHqDoorSource { SyndicateHqDoorFingerprint? CurrentFingerprint { get; } long Generation { get; } int SubscriptionCount { get; } bool TryResolveFresh(out SyndicateHqDoorFingerprint fingerprint); bool TryAttach(); bool Detach(); bool IsCurrent(SyndicateHqDoorFingerprint fingerprint, long generation); bool TryDispose(); void EvictRetainedForLoad(); } public sealed class SyndicateHqListenerRegistry { private readonly Dictionary _registrations = new Dictionary(StringComparer.Ordinal); public int Count => _registrations.Count; public bool TryTrack(ISyndicateHqListenerRegistration registration) { if (registration == null || string.IsNullOrWhiteSpace(registration.StableIdentity) || _registrations.ContainsKey(registration.StableIdentity)) { return false; } _registrations.Add(registration.StableIdentity, registration); return true; } public bool RemoveAll(Action? log = null) { KeyValuePair[] array = _registrations.ToArray(); for (int i = 0; i < array.Length; i++) { KeyValuePair keyValuePair = array[i]; try { if (keyValuePair.Value.TryRemove(out string failure)) { _registrations.Remove(keyValuePair.Key); } else { log?.Invoke("HQ listener removal remained pending for " + keyValuePair.Key + ": " + (failure ?? "unknown failure")); } } catch (Exception ex) { log?.Invoke("HQ listener removal threw for " + keyValuePair.Key + ": " + ex.Message); } } return _registrations.Count == 0; } } public sealed class SyndicateHqDoorSubscriptionState { private readonly SyndicateHqListenerRegistry _registry = new SyndicateHqListenerRegistry(); private bool _detachRequested; private bool _disposed; public long Generation { get; private set; } public int SubscriptionCount => _registry.Count; public bool IsDetachRequested => _detachRequested; public bool IsDisposed => _disposed; public bool CanAttach { get { if (!_disposed) { if (_detachRequested) { return SubscriptionCount <= 0; } return true; } return false; } } public bool TryTrack(ISyndicateHqListenerRegistration registration) { return _registry.TryTrack(registration); } public long BeginAttach() { _detachRequested = false; return ++Generation; } public void RequestDetach() { _detachRequested = true; long generation = Generation + 1; Generation = generation; } public bool TryRemoveAll(Action? log = null) { return _registry.RemoveAll(log); } public void MarkDisposed() { _disposed = true; } } public sealed record SyndicateHqDoorResolution(SyndicateHqDoorFingerprint Fingerprint, InteractableObject InteractableObject); public sealed class SyndicateHqDoorObserver : ISyndicateHqDoorSource, IDisposable { private sealed class SyndicateHqDoorListenerRegistration : ISyndicateHqListenerRegistration { private readonly InteractableObject _target; private readonly UnityAction _listener; private bool _added; private bool _removed; public string StableIdentity { get; } public SyndicateHqDoorListenerRegistration(string stableIdentity, InteractableObject target, UnityAction listener) { StableIdentity = stableIdentity; _target = target; _listener = listener; } public void MarkAdded() { _added = true; } public bool TryRemove(out string? failure) { failure = null; if (_removed) { return true; } if (!_added) { _removed = true; return true; } try { _target.onInteractStart.RemoveListener(_listener); _removed = true; return true; } catch (Exception ex) { failure = ex.Message; return false; } } } private readonly Action _onInteraction; private readonly Action _log; private readonly SyndicateHqDoorSubscriptionState _subscriptionState = new SyndicateHqDoorSubscriptionState(); private SyndicateHqDoorResolution? _resolution; private SyndicateHqDoorResolution? _retainedResolution; public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public SyndicateHqDoorFingerprint? CurrentFingerprint => _resolution?.Fingerprint; public long Generation => _subscriptionState.Generation; public int SubscriptionCount => _subscriptionState.SubscriptionCount; public SyndicateHqDoorObserver(Action onInteraction, Action? log = null) { _onInteraction = onInteraction ?? throw new ArgumentNullException("onInteraction"); _log = log ?? ((Action)delegate { }); } public bool IsCurrent(SyndicateHqDoorFingerprint fingerprint, long generation) { if (!_subscriptionState.IsDisposed && !_subscriptionState.IsDetachRequested && generation == _subscriptionState.Generation && (object)_resolution?.Fingerprint == fingerprint) { return _subscriptionState.SubscriptionCount == 1; } return false; } public bool TryResolveFresh(out SyndicateHqDoorFingerprint fingerprint) { if (_subscriptionState.IsDisposed) { fingerprint = null; return false; } EvictRetainedForLoad(); if (!TryResolveRetainedOrScan(out SyndicateHqDoorResolution resolution, out string _) || (object)resolution == null) { fingerprint = null; return false; } fingerprint = resolution.Fingerprint; return true; } public bool TryAttach() { if (!_subscriptionState.CanAttach) { return false; } if ((object)_resolution != null && _subscriptionState.SubscriptionCount == 1) { return true; } if (!TryResolveRetainedOrScan(out SyndicateHqDoorResolution resolution, out string failure) || (object)resolution == null) { _log(failure); return false; } try { SyndicateHqDoorResolution resolved = resolution; long generation = _subscriptionState.BeginAttach(); UnityAction val = DelegateSupport.ConvertDelegate((Delegate)(Action)delegate { Dispatch(resolved.Fingerprint, generation); }); if (val == null) { throw new InvalidOperationException("UnityAction conversion returned null."); } SyndicateHqDoorListenerRegistration syndicateHqDoorListenerRegistration = new SyndicateHqDoorListenerRegistration(resolved.Fingerprint.DoorPath, resolved.InteractableObject, val); if (!_subscriptionState.TryTrack(syndicateHqDoorListenerRegistration)) { throw new InvalidOperationException("Duplicate exact-door listener registration was rejected."); } _resolution = resolved; syndicateHqDoorListenerRegistration.MarkAdded(); resolved.InteractableObject.onInteractStart.AddListener(val); return true; } catch (Exception ex) { _log("Exact Syndicate HQ door listener registration failed: " + ex.Message); _subscriptionState.RequestDetach(); _subscriptionState.TryRemoveAll(_log); if (_subscriptionState.SubscriptionCount == 0) { _resolution = null; } return false; } } public bool Detach() { _subscriptionState.RequestDetach(); bool num = _subscriptionState.TryRemoveAll(_log); if (num) { _resolution = null; } return num; } public bool TryDispose() { if (_subscriptionState.IsDisposed) { return true; } if (!Detach()) { return false; } _subscriptionState.MarkDisposed(); return true; } void IDisposable.Dispose() { TryDispose(); } private void Dispatch(SyndicateHqDoorFingerprint fingerprint, long generation) { try { if (IsCurrent(fingerprint, generation)) { _onInteraction(fingerprint, generation); } } catch (Exception ex) { _log("Syndicate HQ door callback was isolated: " + ex.Message); } } public void EvictRetainedForLoad() { _retainedResolution = null; } private bool TryResolveRetainedOrScan(out SyndicateHqDoorResolution? resolution, out string failure) { SyndicateHqDoorResolution found = null; string reason = string.Empty; bool result = Timing.Measure("hq/door-resolve", delegate { if ((object)_retainedResolution != null && !IsUnityNull((Object?)(object)_retainedResolution.InteractableObject)) { found = _retainedResolution; reason = "Retained the exact Nightclub door across the boundary."; return true; } if (!TryResolveExact(out SyndicateHqDoorResolution resolution2, out string failure2)) { reason = failure2; return false; } _retainedResolution = resolution2; found = resolution2; reason = "Resolved the exact Nightclub door by a fresh scene scan."; return true; }); resolution = found; failure = reason; return result; } public static bool TryResolveExact(out SyndicateHqDoorResolution? resolution, out string failure) { resolution = null; try { string[] shellPaths = (from gameObject in ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where(delegate(GameObject gameObject) { //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) if (!IsUnityNull((Object?)(object)gameObject)) { Scene scene = gameObject.scene; return ((Scene)(ref scene)).IsValid(); } return false; }) select GetTransformPath(gameObject.transform) into path where string.Equals(path, "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall", StringComparison.Ordinal) select path).ToArray(); StaticDoor[] array = (from door in ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where(delegate(StaticDoor door) { //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) if (!IsUnityNull((Object?)(object)door)) { Scene scene = ((Component)door).gameObject.scene; return ((Scene)(ref scene)).IsValid(); } return false; }) where string.Equals(GetTransformPath(((Component)door).transform), "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall/Facade (1)/Metal Glass Door (Static)", StringComparison.Ordinal) select door).ToArray(); SyndicateHqDoorFingerprint[] doors = array.Select(Capture).ToArray(); SyndicateHqIdentityCheckResult syndicateHqIdentityCheckResult = SyndicateHqIdentity.Validate(new SyndicateHqSceneSnapshot(shellPaths, doors)); if (!syndicateHqIdentityCheckResult.Accepted || array.Length != 1) { failure = syndicateHqIdentityCheckResult.Reason; return false; } InteractableObject intObj = array[0].IntObj; if (IsUnityNull((Object?)(object)intObj)) { failure = "Exact Nightclub door IntObj was unavailable."; return false; } resolution = new SyndicateHqDoorResolution(syndicateHqIdentityCheckResult.Door, intObj); failure = string.Empty; return true; } catch (Exception ex) { failure = "Exact Nightclub door resolution threw: " + ex.Message; return false; } } private static SyndicateHqDoorFingerprint Capture(StaticDoor door) { //IL_0095: 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_00b8: Unknown result type (might be due to invalid IL or missing references) Component intObj = (Component)(object)door.IntObj; object? memberValue = GetMemberValue(door, "Building"); Component val = (Component)((memberValue is Component) ? memberValue : null); object? memberValue2 = GetMemberValue(door, "AccessPoint"); Component val2 = (Component)((memberValue2 is Component) ? memberValue2 : null); return new SyndicateHqDoorFingerprint(GetTransformPath(((Component)door).transform), TypeName(door), GetTransformPath((intObj != null) ? intObj.transform : null), TypeName(intObj), GetTransformPath((val != null) ? val.transform : null), TypeName(val), ReadMemberText(val, "GUID"), GetTransformPath((val2 != null) ? val2.transform : null), ReadInt(door, "doorIndex"), ToValue(((Component)door).transform.position), ToValue(((Component)door).transform.forward), (val2 == null) ? ((SyndicateHqVector3?)null) : new SyndicateHqVector3?(ToValue(val2.transform.position)), GetMemberValue(door, "IntObj") != null, val != null, val2 != null, GetMemberValue(door, "CanKnock") != null, GetMemberValue(door, "Usable") != null); } private static int ReadInt(object value, string name) { object memberValue = GetMemberValue(value, name); if (memberValue is int) { return (int)memberValue; } return int.MinValue; } private static string ReadMemberText(object? value, string name) { return GetMemberValue(value, name)?.ToString() ?? string.Empty; } private static object? GetMemberValue(object? value, string name) { if (value == null) { return null; } Type type = value.GetType(); object obj = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(value); if (obj == null) { FieldInfo? field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)field == null) { return null; } obj = field.GetValue(value); } return obj; } private static SyndicateHqVector3 ToValue(Vector3 value) { //IL_0000: 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) return new SyndicateHqVector3(value.x, value.y, value.z); } private static string TypeName(object? value) { return value?.GetType().FullName ?? string.Empty; } private static bool IsUnityNull(object? value) { if (value != null) { return value == null; } return true; } private static bool IsUnityNull(Object? value) { return value == (Object)null; } private static string GetTransformPath(Transform? transform) { if (IsUnityNull((Object?)(object)transform)) { return string.Empty; } List list = new List(); Transform val = transform; while (!IsUnityNull((Object?)(object)val)) { list.Add(((Object)val).name); val = val.parent; } list.Reverse(); return string.Join("/", list); } } public sealed class SyndicateHqHostContextAdapter : IRelease1StoryHostContext { private readonly Guid _sessionEpoch = Guid.NewGuid(); private long _loadEpoch; public void BeginLoad() { _loadEpoch++; } public Release1StoryHostContextReadStatus TryRead(out Release1StoryHostContextSnapshot snapshot) { snapshot = default(Release1StoryHostContextSnapshot); try { ServerManager serverManager = InstanceFinder.ServerManager; ClientManager clientManager = InstanceFinder.ClientManager; if (serverManager == null || clientManager == null) { return Release1StoryHostContextReadStatus.Pending; } if (!serverManager.OneServerStarted()) { return Release1StoryHostContextReadStatus.NotAuthoritative; } if (!clientManager.Started) { return Release1StoryHostContextReadStatus.Pending; } LoadManager instance = Singleton.Instance; string activeSaveFolder = ((instance != null) ? instance.LoadedGameFolderPath : null); if (!LocalPressureHostLifecyclePolicies.TryGetCanonicalHostIdentity(activeSaveFolder, out string identity)) { return Release1StoryHostContextReadStatus.Pending; } List list = new List(); List playerList = Player.PlayerList; if (playerList == null) { return Release1StoryHostContextReadStatus.Pending; } Enumerator enumerator = playerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if (current != null && ((NetworkBehaviour)current).IsServerInitialized) { list.Add(current); } } if (list.Count == 0) { return Release1StoryHostContextReadStatus.Pending; } if (list.Count > 1) { return Release1StoryHostContextReadStatus.UnsupportedMultiplayer; } string text = list[0].PlayerCode?.Trim(); if (list[0].Connection == null || LocalPressureHostLifecyclePolicies.IsPlaceholderPlayerCode(text)) { return Release1StoryHostContextReadStatus.Pending; } if (!string.Equals(identity, text, StringComparison.Ordinal)) { return Release1StoryHostContextReadStatus.AmbiguousIdentity; } snapshot = new Release1StoryHostContextSnapshot(_sessionEpoch, _loadEpoch, identity, activeSaveFolder); return Release1StoryHostContextReadStatus.Ready; } catch { snapshot = default(Release1StoryHostContextSnapshot); return Release1StoryHostContextReadStatus.Faulted; } } } public sealed class SyndicateHqInteriorRuntime : ISyndicateHqInterior, IDisposable { private readonly Action _log; private readonly ISyndicateHqStorageRuntime? _storage; private GameObject? _root; private readonly Dictionary _markers = new Dictionary(StringComparer.Ordinal); private readonly Dictionary _materials = new Dictionary(); private bool _destroyPending; private bool _disposed; public bool IsCreated { get { string reason; if (!_destroyPending && !IsUnityNull((Object?)(object)_root)) { return ValidateOwnedRuntime(out reason); } return false; } } public bool IsDestroyPending => _destroyPending; public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public SyndicateHqInteriorRuntime(Action? log = null, ISyndicateHqStorageRuntime? storage = null) { _log = log ?? ((Action)delegate { }); _storage = storage; } public bool TryEnsure(SyndicateHqVector3 exteriorAnchor) { return Timing.Measure("hq-interior-create", () => TryEnsureCore(exteriorAnchor)); } private bool TryEnsureCore(SyndicateHqVector3 exteriorAnchor) { //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_0128: Expected O, but got Unknown //IL_013d: 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_024a: Expected O, but got Unknown //IL_0299: Unknown result type (might be due to invalid IL or missing references) if (_disposed) { _log("Syndicate HQ interior creation was rejected after disposal."); return false; } if (_destroyPending) { if (!IsUnityNull((Object?)(object)_root)) { _log("Syndicate HQ interior creation is waiting for deferred root destruction."); return false; } _root = null; _destroyPending = false; } string reason; if (!IsUnityNull((Object?)(object)_root)) { return ValidateOwnedRuntime(out reason); } if (!SyndicateHqInteriorDefinition.IsValid(out string reason2)) { _log("Syndicate HQ interior definition was invalid: " + reason2); return false; } string value = "scene snapshot"; try { GameObject[] array = SnapshotSceneObjects(); GameObject[] array2 = array.Where((GameObject gameObject) => string.Equals(((Object)gameObject).name, "OC_SyndicateHQ_Interior", StringComparison.Ordinal)).ToArray(); if (array2.Length != 0) { _log($"Syndicate HQ interior creation found {array2.Length} foreign or stale root(s)."); return false; } value = "root creation"; _root = new GameObject("OC_SyndicateHQ_Interior") { hideFlags = (HideFlags)52 }; SyndicateHqVector3 pocketRoot = SyndicateHqInteriorDefinition.GetPocketRoot(exteriorAnchor); _root.transform.position = ToUnity(pocketRoot); value = "material palette"; CreateMaterialPalette(array); foreach (SyndicateHqPrimitiveDefinition primitive in SyndicateHqInteriorDefinition.Primitives) { value = "primitive '" + primitive.Name + "'"; BuildPrimitive(primitive); } Dictionary dictionary = ResolveNativePropSources(array); foreach (SyndicateHqNativePropDefinition nativeProp in SyndicateHqInteriorDefinition.NativeProps) { value = "native prop '" + nativeProp.Name + "'"; if (dictionary.TryGetValue(nativeProp.Name, out var value2)) { BuildNativeProp(nativeProp, value2); } } foreach (SyndicateHqMarkerDefinition marker in SyndicateHqInteriorDefinition.Markers) { value = "marker '" + marker.Name + "'"; GameObject val = new GameObject(marker.Name); val.transform.SetParent(_root.transform, false); val.transform.localPosition = new Vector3(marker.Position.X, marker.Position.Y, marker.Position.Z); _markers.Add(marker.Name, val.transform); } foreach (SyndicateHqLightDefinition light in SyndicateHqInteriorDefinition.Lights) { value = "light '" + light.Name + "'"; BuildLight(light); } value = "physics synchronization"; Physics.SyncTransforms(); value = "owned-runtime validation"; if (ValidateOwnedRuntime(out string reason3)) { SyndicateHqStorageResult syndicateHqStorageResult = _storage?.PlaceAtPocket(pocketRoot); if ((object)syndicateHqStorageResult != null && !syndicateHqStorageResult.IsReady) { _log($"Syndicate HQ native storage remained inert: {syndicateHqStorageResult.Status} — {syndicateHqStorageResult.Reason}"); } return true; } _log("Syndicate HQ interior construction failed owned-runtime validation: " + reason3); Destroy(); return false; } catch (Exception ex) { _log($"Syndicate HQ interior construction failed at {value}: {ex.GetType().Name}: {ex.Message}"); Destroy(); return false; } } public bool TryGetMarker(string name, out SyndicateHqVector3 position) { //IL_001a: 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_0030: Unknown result type (might be due to invalid IL or missing references) if (_markers.TryGetValue(name, out Transform value) && !IsUnityNull((Object?)(object)value)) { position = new SyndicateHqVector3(value.position.x, value.position.y, value.position.z); return true; } position = default(SyndicateHqVector3); return false; } public void Destroy() { Timing.Measure("hq-interior-destroy", DestroyCore); } private void DestroyCore() { _storage?.DisableInteractions(); _markers.Clear(); if (!IsUnityNull((Object?)(object)_root) && !_destroyPending) { Object.Destroy((Object)(object)_root); _destroyPending = true; } if (IsUnityNull((Object?)(object)_root)) { _root = null; _destroyPending = false; } foreach (Material value in _materials.Values) { if (!IsUnityNull((Object?)(object)value)) { Object.Destroy((Object)(object)value); } } _materials.Clear(); } public void Dispose() { if (!_disposed) { _disposed = true; Destroy(); } } private void BuildPrimitive(SyndicateHqPrimitiveDefinition definition) { //IL_0035: 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) GameObject obj = GameObject.CreatePrimitive((PrimitiveType)3); ((Object)obj).name = definition.Name; obj.transform.SetParent(_root.transform, false); obj.transform.localPosition = ToUnity(definition.Position); obj.transform.localScale = ToUnity(definition.Scale); MeshRenderer component = obj.GetComponent(); if (!IsUnityNull((Object?)(object)component)) { ((Renderer)component).sharedMaterial = _materials[definition.MaterialRole]; ((Renderer)component).enabled = definition.IsVisible; ((Renderer)component).receiveShadows = true; ((Renderer)component).shadowCastingMode = (ShadowCastingMode)1; } Collider component2 = obj.GetComponent(); if (!definition.HasCollider && !IsUnityNull((Object?)(object)component2)) { component2.enabled = false; Object.Destroy((Object)(object)component2); } } private void BuildLight(SyndicateHqLightDefinition definition) { //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_0022: 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_0074: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject(definition.Name); val.transform.SetParent(_root.transform, false); val.transform.localPosition = ToUnity(definition.Position); Light obj = val.AddComponent(); obj.type = (LightType)2; obj.color = new Color(definition.Color.R, definition.Color.G, definition.Color.B, 1f); obj.intensity = definition.Intensity; obj.range = definition.Range; obj.shadows = (LightShadows)0; } private static GameObject[] SnapshotSceneObjects() { return ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where(delegate(GameObject gameObject) { //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) if (!IsUnityNull((Object?)(object)gameObject)) { Scene scene = gameObject.scene; return ((Scene)(ref scene)).IsValid(); } return false; }).ToArray(); } private static string LeafNameOf(string hierarchyPath) { int num = hierarchyPath.LastIndexOf('/'); if (num >= 0) { int num2 = num + 1; return hierarchyPath.Substring(num2, hierarchyPath.Length - num2); } return hierarchyPath; } private void CreateMaterialPalette(GameObject[] sceneObjects) { GameObject shellRoot = ResolveNightclubShellRoot(sceneObjects); foreach (SyndicateHqMaterialRole item in SyndicateHqInteriorDefinition.Primitives.Select((SyndicateHqPrimitiveDefinition item) => item.MaterialRole).Distinct()) { _materials.Add(item, CreateMaterial(item, shellRoot)); } } private Dictionary ResolveNativePropSources(GameObject[] sceneObjects) { HashSet catalogPaths = SyndicateHqInteriorDefinition.NativeProps.Select((SyndicateHqNativePropDefinition item) => item.SourceHierarchyPath).ToHashSet(StringComparer.Ordinal); HashSet catalogLeafNames = catalogPaths.Select(LeafNameOf).ToHashSet(StringComparer.Ordinal); Dictionary dictionary = (from gameObject in sceneObjects where catalogLeafNames.Contains(((Object)gameObject).name) select new { GameObject = gameObject, Path = BuildHierarchyPath(gameObject.transform) } into candidate where catalogPaths.Contains(candidate.Path) select candidate).GroupBy(candidate => candidate.Path, StringComparer.Ordinal).ToDictionary(group => group.Key, group => group.Select(candidate => candidate.GameObject).ToArray(), StringComparer.Ordinal); Dictionary dictionary2 = new Dictionary(StringComparer.Ordinal); foreach (SyndicateHqNativePropDefinition nativeProp in SyndicateHqInteriorDefinition.NativeProps) { if (!dictionary.TryGetValue(nativeProp.SourceHierarchyPath, out var value) || value.Length != 1) { if (nativeProp.IsRequired) { throw new InvalidOperationException($"Native HQ prop '{nativeProp.Name}' resolved {((value != null) ? value.Length : 0)} source objects at '{nativeProp.SourceHierarchyPath}'."); } _log("Optional native HQ prop '" + nativeProp.Name + "' was unavailable and was omitted."); } else { dictionary2.Add(nativeProp.Name, value[0]); } } return dictionary2; } private void BuildNativeProp(SyndicateHqNativePropDefinition definition, GameObject source) { //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_0014: Expected O, but got Unknown //IL_0037: 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_0069: 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_00ee: 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_0110: 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_014c: 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_0153: Unknown result type (might be due to invalid IL or missing references) //IL_0159: 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_0172: Unknown result type (might be due to invalid IL or missing references) //IL_017e: 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_019b: Unknown result type (might be due to invalid IL or missing references) //IL_01a7: 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_01d4: 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) GameObject val = new GameObject(definition.Name) { hideFlags = (HideFlags)52 }; val.transform.SetParent(_root.transform, false); val.transform.localPosition = ToUnity(definition.Position); val.transform.localRotation = Quaternion.Euler(definition.PitchDegrees, definition.YawDegrees, definition.RollDegrees); val.transform.localScale = Vector3.one * definition.UniformScale; CloneVisualTree(source.transform, val.transform, isRoot: true, definition.KeepColliders, definition.IncludeInactiveChildren); Renderer[] array = ((IEnumerable)val.GetComponentsInChildren(true)).Where((Renderer renderer) => !IsUnityNull((Object?)(object)renderer)).ToArray(); if (array.Length == 0) { throw new InvalidOperationException("Native HQ prop '" + definition.Name + "' had no visual renderers."); } Bounds bounds = array[0].bounds; foreach (Renderer item in array.Skip(1)) { ((Bounds)(ref bounds)).Encapsulate(item.bounds); } Vector3 val2 = _root.transform.TransformPoint(ToUnity(definition.Position)); SyndicateHqVector3 value = SyndicateHqNativePropPlacement.AlignmentOffset(new SyndicateHqVector3(val2.x, val2.y, val2.z), new SyndicateHqBoundsSnapshot(new SyndicateHqVector3(((Bounds)(ref bounds)).center.x, ((Bounds)(ref bounds)).center.y, ((Bounds)(ref bounds)).center.z), new SyndicateHqVector3(((Bounds)(ref bounds)).min.x, ((Bounds)(ref bounds)).min.y, ((Bounds)(ref bounds)).min.z)), definition.Anchor); Transform transform = val.transform; transform.position += ToUnity(value); } private static void CloneVisualTree(Transform source, Transform parent, bool isRoot, bool keepColliders, bool includeInactiveChildren) { //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_001e: Expected O, but got Unknown //IL_003c: 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_0057: 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_0068: 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) GameObject val = new GameObject(isRoot ? "Visual" : ((Object)source).name) { hideFlags = (HideFlags)52 }; val.transform.SetParent(parent, false); val.transform.localPosition = (isRoot ? Vector3.zero : source.localPosition); val.transform.localRotation = (isRoot ? Quaternion.identity : source.localRotation); val.transform.localScale = source.localScale; MeshFilter component = ((Component)source).GetComponent(); if (!IsUnityNull((Object?)(object)component) && !IsUnityNull((Object?)(object)component.sharedMesh)) { val.AddComponent().sharedMesh = component.sharedMesh; } MeshRenderer component2 = ((Component)source).GetComponent(); if (!IsUnityNull((Object?)(object)component2)) { MeshRenderer obj = val.AddComponent(); ((Renderer)obj).sharedMaterials = ((Renderer)component2).sharedMaterials; ((Renderer)obj).enabled = ((Renderer)component2).enabled; ((Renderer)obj).shadowCastingMode = ((Renderer)component2).shadowCastingMode; ((Renderer)obj).receiveShadows = ((Renderer)component2).receiveShadows; } if (keepColliders) { CopyVisualColliders(source, val); } for (int i = 0; i < source.childCount; i++) { Transform child = source.GetChild(i); if (!IsUnityNull((Object?)(object)child) && (includeInactiveChildren || ((Component)child).gameObject.activeSelf)) { CloneVisualTree(child, val.transform, isRoot: false, keepColliders, includeInactiveChildren); } } } private static void CopyVisualColliders(Transform source, GameObject destination) { //IL_0041: 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_00b2: Unknown result type (might be due to invalid IL or missing references) //IL_0127: Unknown result type (might be due to invalid IL or missing references) foreach (BoxCollider item in ((IEnumerable)((Component)source).GetComponents()).Where((BoxCollider collider) => !IsUnityNull((Object?)(object)collider) && ((Collider)collider).enabled)) { BoxCollider obj = destination.AddComponent(); obj.center = item.center; obj.size = item.size; ((Collider)obj).isTrigger = false; } foreach (SphereCollider item2 in ((IEnumerable)((Component)source).GetComponents()).Where((SphereCollider collider) => !IsUnityNull((Object?)(object)collider) && ((Collider)collider).enabled)) { SphereCollider obj2 = destination.AddComponent(); obj2.center = item2.center; obj2.radius = item2.radius; ((Collider)obj2).isTrigger = false; } foreach (CapsuleCollider item3 in ((IEnumerable)((Component)source).GetComponents()).Where((CapsuleCollider collider) => !IsUnityNull((Object?)(object)collider) && ((Collider)collider).enabled)) { CapsuleCollider obj3 = destination.AddComponent(); obj3.center = item3.center; obj3.radius = item3.radius; obj3.height = item3.height; obj3.direction = item3.direction; ((Collider)obj3).isTrigger = false; } foreach (MeshCollider item4 in ((IEnumerable)((Component)source).GetComponents()).Where((MeshCollider collider) => !IsUnityNull((Object?)(object)collider) && ((Collider)collider).enabled && !IsUnityNull((Object?)(object)collider.sharedMesh))) { MeshCollider obj4 = destination.AddComponent(); obj4.sharedMesh = item4.sharedMesh; obj4.convex = item4.convex; ((Collider)obj4).isTrigger = false; } } private static Material CreateMaterial(SyndicateHqMaterialRole role, GameObject? shellRoot) { //IL_0036: Unknown result type (might be due to invalid IL or missing references) //IL_006a: 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) //IL_0083: Unknown result type (might be due to invalid IL or missing references) //IL_009c: 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_0104: Unknown result type (might be due to invalid IL or missing references) Material val = FindNativeMaterial(role, shellRoot); Shader val2 = Shader.Find("Universal Render Pipeline/Lit") ?? Shader.Find("Standard"); if (val == null && val2 == null) { throw new InvalidOperationException("No usable native or fallback shader was available for the HQ interior."); } Material val3 = (Material)((val != null) ? ((object)Object.Instantiate(val)) : ((object)new Material(val2))); ((Object)val3).name = "OC_SyndicateHQ_" + role; ((Object)val3).hideFlags = (HideFlags)52; Color val4 = ColorFor(role); if (val3.HasProperty("_BaseColor")) { val3.SetColor("_BaseColor", val4); } if (val3.HasProperty("_Color")) { val3.SetColor("_Color", val4); } if (val3.HasProperty("_Cull")) { val3.SetFloat("_Cull", 0f); } if (val3.HasProperty("_CullMode")) { val3.SetFloat("_CullMode", 0f); } if (role == SyndicateHqMaterialRole.Accent && val3.HasProperty("_EmissionColor")) { val3.EnableKeyword("_EMISSION"); val3.SetColor("_EmissionColor", val4 * 1.5f); } val3.renderQueue = 2000; val3.SetOverrideTag("RenderType", "Opaque"); val3.doubleSidedGI = true; return val3; } private static GameObject? ResolveNightclubShellRoot(GameObject[] sceneObjects) { try { string shellLeafName = LeafNameOf("Map/Hyland Point/Region_Northtown/Nightclub/desert town hall"); GameObject[] array = sceneObjects.Where((GameObject gameObject) => string.Equals(((Object)gameObject).name, shellLeafName, StringComparison.Ordinal) && string.Equals(BuildHierarchyPath(gameObject.transform), "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall", StringComparison.Ordinal)).ToArray(); return (array.Length == 1) ? array[0] : null; } catch { return null; } } private static Material? FindNativeMaterial(SyndicateHqMaterialRole role, GameObject? shellRoot) { string[] array; switch (role) { case SyndicateHqMaterialRole.Accent: return null; case SyndicateHqMaterialRole.Floor: array = new string[3] { "floor", "concrete", "tile" }; break; case SyndicateHqMaterialRole.Brick: array = new string[2] { "brick", "masonry" }; break; case SyndicateHqMaterialRole.Ceiling: array = new string[3] { "ceiling", "plaster", "concrete" }; break; case SyndicateHqMaterialRole.Wood: array = new string[2] { "wood", "timber" }; break; case SyndicateHqMaterialRole.Metal: array = new string[2] { "metal", "steel" }; break; default: array = Array.Empty(); break; } string[] keywords = array; try { if (IsUnityNull((Object?)(object)shellRoot)) { return null; } return (from material in ((IEnumerable)shellRoot.GetComponentsInChildren(true)).Where((Renderer renderer) => !IsUnityNull((Object?)(object)renderer)).SelectMany((Renderer renderer) => (IEnumerable)renderer.sharedMaterials) where !IsUnityNull((Object?)(object)material) && keywords.Any((string keyword) => ((Object)material).name.Contains(keyword, StringComparison.OrdinalIgnoreCase)) select material).OrderBy((Material material) => ((Object)material).name, StringComparer.Ordinal).FirstOrDefault(); } catch { return null; } } private static Color ColorFor(SyndicateHqMaterialRole role) { //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //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_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) //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_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) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00c2: 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) return (Color)(role switch { SyndicateHqMaterialRole.Floor => new Color(0.1f, 0.11f, 0.12f, 1f), SyndicateHqMaterialRole.Brick => new Color(0.31f, 0.16f, 0.1f, 1f), SyndicateHqMaterialRole.Ceiling => new Color(0.075f, 0.08f, 0.09f, 1f), SyndicateHqMaterialRole.Wood => new Color(0.22f, 0.095f, 0.045f, 1f), SyndicateHqMaterialRole.Metal => new Color(0.18f, 0.2f, 0.22f, 1f), _ => new Color(0.86f, 0.36f, 0.07f, 1f), }); } private bool ValidateOwnedRuntime(out string reason) { if (IsUnityNull((Object?)(object)_root)) { reason = "the owned root was unavailable"; return false; } if (_destroyPending) { reason = "the owned root was pending destruction"; return false; } if (!string.Equals(((Object)_root).name, "OC_SyndicateHQ_Interior", StringComparison.Ordinal)) { reason = "the owned root name was '" + ((Object)_root).name + "'"; return false; } GameObject[] source = ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where(delegate(GameObject gameObject) { //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) if (!IsUnityNull((Object?)(object)gameObject)) { Scene scene = gameObject.scene; if (((Scene)(ref scene)).IsValid()) { return string.Equals(((Object)gameObject).name, "OC_SyndicateHQ_Interior", StringComparison.Ordinal); } } return false; }).ToArray(); if (!SyndicateHqInteriorOwnership.IsRetainedNativeRoot(((Object)_root).GetInstanceID(), source.Select((GameObject gameObject) => ((Object)gameObject).GetInstanceID()).ToArray(), out reason)) { return false; } foreach (SyndicateHqMarkerDefinition marker in SyndicateHqInteriorDefinition.Markers) { if (IsUnityNull((Object?)(object)_root.transform.Find(marker.Name))) { reason = "marker '" + marker.Name + "' was unavailable"; return false; } } foreach (string requiredColliderName in SyndicateHqInteriorDefinition.RequiredColliderNames) { Transform val = _root.transform.Find(requiredColliderName); if (IsUnityNull((Object?)(object)val)) { reason = "required geometry '" + requiredColliderName + "' was unavailable"; return false; } if (IsUnityNull((Object?)(object)((Component)val).GetComponent())) { reason = "required collider '" + requiredColliderName + "' was unavailable"; return false; } } foreach (SyndicateHqNativePropDefinition item in SyndicateHqInteriorDefinition.NativeProps.Where((SyndicateHqNativePropDefinition item) => item.IsRequired)) { Transform val2 = _root.transform.Find(item.Name); if (IsUnityNull((Object?)(object)val2)) { reason = "required native prop '" + item.Name + "' was unavailable"; return false; } if (((IEnumerable)((Component)val2).GetComponentsInChildren(true)).All(IsUnityNull)) { reason = "required native prop '" + item.Name + "' had no renderer"; return false; } if (item.KeepColliders && ((IEnumerable)((Component)val2).GetComponentsInChildren(true)).All(IsUnityNull)) { reason = "required native prop '" + item.Name + "' had no collider"; return false; } } foreach (SyndicateHqLightDefinition light in SyndicateHqInteriorDefinition.Lights) { Transform val3 = _root.transform.Find(light.Name); if (IsUnityNull((Object?)(object)val3)) { reason = "light object '" + light.Name + "' was unavailable"; return false; } if (IsUnityNull((Object?)(object)((Component)val3).GetComponent())) { reason = "light component '" + light.Name + "' was unavailable"; return false; } } reason = "the exact owned root, markers, collision shell, and lights were present"; return true; } private static string BuildHierarchyPath(Transform transform) { Stack stack = new Stack(); Transform val = transform; int num = 0; while (!IsUnityNull((Object?)(object)val) && num++ < 64) { stack.Push(((Object)val).name); val = val.parent; } return string.Join("/", stack); } private static Vector3 ToUnity(SyndicateHqVector3 value) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(value.X, value.Y, value.Z); } private static bool IsUnityNull(object? value) { if (value != null) { return value == null; } return true; } private static bool IsUnityNull(Object? value) { return value == (Object)null; } } public enum SyndicateHqNativeStorageLookupStatus { Missing, Restored, IdentityConflict } public interface ISyndicateHqNativeStorageBoundary { bool TryPrepareGrid(out object grid, out string reason); void EvictRetainedClosetsForLoad(); SyndicateHqNativeStorageLookupStatus FindRestoredCloset(SyndicateHqStorageDefinition definition, out object entity, out string reason); bool TryCreate(object grid, SyndicateHqStorageDefinition definition, out object entity, out string reason); bool TryValidate(object entity, SyndicateHqStorageDefinition definition, out string reason); bool TryReadClosetSlots(SyndicateHqStorageDefinition definition, out IReadOnlyList slots, out string reason); Release1SmallCourtesyWorldReadStatus TryReadClosetSlotCashBalance(SyndicateHqStorageDefinition definition, int slotIndex, out float balance, out string reason); Release1SmallCourtesyWorldMutationStatus TrySetClosetSlotLocked(SyndicateHqStorageDefinition definition, int slotIndex, bool locked, out string reason); Release1SmallCourtesyWorldMutationStatus TryChangeClosetSlotCashBalance(SyndicateHqStorageDefinition definition, int slotIndex, float amount, out string reason); void ReleaseOwnedClosetSlotLocks(); bool TryPlace(object entity, SyndicateHqVector3 worldPosition, float yawDegrees, out string reason); void SetInteractionEnabled(object entity, bool enabled); bool TryCleanup(object entity, out string reason); } public sealed class SyndicateHqNativeStorageBoundary : ISyndicateHqNativeStorageBoundary { private const string GridRootName = "OC_SyndicateHQ_StorageGrid"; private const string GridGuid = "7ce4e626-4438-48c4-a88a-c1b47f99d66f"; private const int GridWidth = 16; private const int GridDepth = 32; private const BindingFlags MemberFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic; private static readonly Vector3 PocketRoot = new Vector3(956.254f, -4.000184f, 1156.474f); private readonly Dictionary _closets = new Dictionary(); private readonly HashSet _conflictedClosetGuids = new HashSet(); private bool _closetsScannedForLoad; private readonly HashSet<(Guid Guid, int SlotIndex)> _ownedClosetSlotLocks = new HashSet<(Guid, int)>(); private const float ClosetCashBalanceTolerance = 0.01f; private GameObject? _gridProxy; private Grid? _grid; private Property? _parentProperty; private const string UnpackagedId = "unpackaged"; public OrganizedCrimeTimingReceipts Timing { get; set; } = OrganizedCrimeTimingReceipts.Disabled; public bool TryPrepareGrid(out object grid, out string reason) { //IL_012c: Unknown result type (might be due to invalid IL or missing references) //IL_0133: Expected O, but got Unknown grid = null; try { if (IsPreparedGrid(_grid)) { grid = _grid; reason = "Retained native HQ Grid is ready."; return true; } _grid = null; _gridProxy = null; _parentProperty = null; Grid val = FindGridByGuid("7ce4e626-4438-48c4-a88a-c1b47f99d66f"); if (IsPreparedGrid(val)) { RetainGrid(val); grid = val; reason = "Adopted the existing native HQ Grid."; return true; } if (IsLegacyGrid(val)) { if (!TryUpgradeLegacyGrid(val, out reason)) { return false; } RetainGrid(val); grid = val; return true; } if (!IsUnityNull((Object?)(object)val)) { reason = "An existing HQ Grid used the reserved GUID but failed its Property or tile validation."; return false; } int tileCount; Grid val2 = FindLargestOwnedGrid(out tileCount); Property val3 = ((val2 != null) ? val2.ParentProperty : null); Tile val4 = ((val2 != null) ? ((IEnumerable)((Component)val2).GetComponentsInChildren(true)).FirstOrDefault((Func)((Tile tile) => !IsUnityNull((Object?)(object)tile) && !IsUnityNull((Object?)(object)((Component)tile).gameObject))) : null); if (IsUnityNull((Object?)(object)val2) || IsUnityNull((Object?)(object)val3) || IsUnityNull((Object?)(object)val4)) { reason = "No owned vanilla Property Grid with a native Tile was available."; return false; } GameObject val5 = null; GameObject val6 = null; Grid val7 = null; try { val5 = new GameObject("OC_SyndicateHQ_StorageGrid_Staging"); val5.transform.SetParent(((Component)val3).transform, false); val5.SetActive(false); val6 = Object.Instantiate(((Component)val2).gameObject, val5.transform, false); ((Object)val6).name = "OC_SyndicateHQ_StorageGrid"; val6.SetActive(false); val7 = val6.GetComponent(); if (IsUnityNull((Object?)(object)val7)) { throw new InvalidOperationException("The owned Grid clone lost its Grid component."); } if (!TrySetMember(val7, "_guid", "7ce4e626-4438-48c4-a88a-c1b47f99d66f") || !TrySetMember(val7, "_parentProperty", val3)) { throw new InvalidOperationException("The HQ Grid identity could not be assigned before Awake."); } ReplaceTilesWithDeterministicFootprint(val6, val7, val4); DisableProxyVisualsAndColliders(val6); PositionGrid(val6.transform); val6.transform.SetParent(((Component)val3).transform, true); val6.SetActive(true); Tile[] tiles = ((IEnumerable)val6.GetComponentsInChildren(true)).Where((Tile tile) => !IsUnityNull((Object?)(object)tile)).ToArray(); RebuildNativeTopology(val7, tiles); RegisterPropertyGrid(val3, val7); if (!IsPreparedGrid(val7)) { throw new InvalidOperationException("The native HQ Grid failed its GUID, Property, or tile validation."); } RetainGrid(val7); val6 = null; Object.DestroyImmediate((Object)(object)val5); val5 = null; Physics.SyncTransforms(); grid = val7; reason = $"Prepared a hidden {16}x{32} native HQ Grid from an owned Property Grid ({tileCount} source tiles)."; return true; } catch { if (!IsUnityNull((Object?)(object)val7) && !IsUnityNull((object?)((val3 != null) ? val3.Grids : null))) { val3.Grids.Remove(val7); } if (!IsUnityNull((Object?)(object)val6)) { Object.DestroyImmediate((Object)(object)val6); } if (!IsUnityNull((Object?)(object)val5)) { Object.DestroyImmediate((Object)(object)val5); } throw; } } catch (Exception ex) { reason = "Native HQ Grid preparation threw " + ex.GetType().Name + ": " + ex.Message; return false; } } public void EvictRetainedClosetsForLoad() { _closets.Clear(); _conflictedClosetGuids.Clear(); _closetsScannedForLoad = false; } public SyndicateHqNativeStorageLookupStatus FindRestoredCloset(SyndicateHqStorageDefinition definition, out object entity, out string reason) { entity = null; SyndicateHqNativeStorageLookupStatus status = SyndicateHqNativeStorageLookupStatus.Missing; string localReason = string.Empty; PlaceableStorageEntity localEntity = null; Timing.Measure("hq/closet-resolve/" + definition.Name, delegate { try { if (!_closetsScannedForLoad) { ScanAndRetainClosets(); } if (_conflictedClosetGuids.Contains(definition.Guid)) { localReason = $"Multiple restored vanilla closets matched {definition.Guid:D}."; status = SyndicateHqNativeStorageLookupStatus.IdentityConflict; } else { PlaceableStorageEntity valueOrDefault = _closets.GetValueOrDefault(definition.Guid); if (valueOrDefault == null || IsUnityNull((Object?)(object)valueOrDefault)) { localReason = $"No restored vanilla closet matched {definition.Guid:D}."; } else { PrepareCompleteCloset(valueOrDefault, definition, enabled: false); if (!TryValidate(valueOrDefault, definition, out localReason)) { status = SyndicateHqNativeStorageLookupStatus.IdentityConflict; } else { localEntity = valueOrDefault; localReason = "Retained the restored vanilla closet '" + definition.Name + "' across the boundary."; status = SyndicateHqNativeStorageLookupStatus.Restored; } } } } catch (Exception ex) { localReason = "Restored vanilla closet lookup threw " + ex.GetType().Name + ": " + ex.Message; status = SyndicateHqNativeStorageLookupStatus.IdentityConflict; } }); entity = localEntity; reason = localReason; return status; } private void ScanAndRetainClosets() { _closetsScannedForLoad = true; HashSet wanted = SyndicateHqStorageContract.Definitions.Select((SyndicateHqStorageDefinition candidate) => candidate.Guid).ToHashSet(); Guid result; foreach (IGrouping item in from storage in (IEnumerable)Resources.FindObjectsOfTypeAll() where !IsUnityNull((Object?)(object)storage) && !IsUnityNull((Object?)(object)((Component)storage).gameObject) && Guid.TryParse(((object)((BuildableItem)storage).GUID/*cast due to .constrained prefix*/).ToString(), out result) && wanted.Contains(result) group storage by Guid.Parse(((object)((BuildableItem)storage).GUID/*cast due to .constrained prefix*/).ToString())) { PlaceableStorageEntity[] array = item.ToArray(); if (array.Length != 1) { _conflictedClosetGuids.Add(item.Key); } else { _closets[item.Key] = array[0]; } } } public bool TryCreate(object grid, SyndicateHqStorageDefinition definition, out object entity, out string reason) { //IL_006e: Unknown result type (might be due to invalid IL or missing references) entity = null; GridItem val = null; try { Grid val2 = (Grid)((grid is Grid) ? grid : null); if (val2 == null || (Object)(object)val2 != (Object)(object)_grid || !IsPreparedGrid(val2)) { reason = "The complete vanilla closet was not given the retained native HQ Grid."; return false; } ItemDefinition definition2 = ItemManager.GetDefinition(definition.ItemId); if (definition2 == null) { reason = "Vanilla item '" + definition.ItemId + "' was unavailable."; return false; } GameObject obj = BuildManager.CreateGridItem(definition2.CreateInstance(1), val2, CoordinateFor(definition), NormalizeGridRotation(definition.YawDegrees), definition.Guid.ToString("D")); val = ((obj != null) ? obj.GetComponent() : null); PlaceableStorageEntity val3 = ((obj != null) ? obj.GetComponent() : null); if (IsUnityNull((Object?)(object)val) || IsUnityNull((Object?)(object)val3)) { reason = "Vanilla placement did not produce a complete PlaceableStorageEntity prefab."; RollbackCreated(val); return false; } PrepareCompleteCloset(val3, definition, enabled: false); if (!TryValidate(val3, definition, out reason)) { RollbackCreated(val); return false; } _closets[definition.Guid] = val3; entity = val3; reason = "Created standard vanilla closet '" + definition.Name + "' on the native HQ Grid."; return true; } catch (Exception ex) { RollbackCreated(val); reason = "Standard vanilla closet creation threw " + ex.GetType().Name + ": " + ex.Message; return false; } } public bool TryValidate(object entity, SyndicateHqStorageDefinition definition, out string reason) { //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) try { PlaceableStorageEntity val = (PlaceableStorageEntity)((entity is PlaceableStorageEntity) ? entity : null); if (val == null || IsUnityNull((Object?)(object)val) || IsUnityNull((Object?)(object)((Component)val).gameObject) || IsUnityNull((Object?)(object)val.StorageEntity) || IsUnityNull((Object?)(object)((GridItem)val).OwnerGrid) || (Object)(object)((GridItem)val).OwnerGrid != (Object)(object)_grid || IsUnityNull((Object?)(object)((BuildableItem)val).ParentProperty) || (Object)(object)((BuildableItem)val).ParentProperty != (Object)(object)_parentProperty || !Guid.TryParse(((object)((BuildableItem)val).GUID/*cast due to .constrained prefix*/).ToString(), out var result) || result != definition.Guid || val.StorageEntity.SlotCount != definition.RequiredSlotCount || val.StorageEntity.ItemSlots == null || val.StorageEntity.ItemSlots.Count != definition.RequiredSlotCount || val.AccessPoints == null || ((Il2CppArrayBase)(object)val.AccessPoints).Length == 0 || ((IEnumerable)((Component)val).GetComponentsInChildren(true)).All(IsUnityNull)) { reason = "The native closet was missing its exact GUID, Grid/Property ownership, 20 slots, access points, or colliders."; return false; } reason = "Validated complete vanilla closet '" + definition.Name + "'."; return true; } catch (Exception ex) { reason = "Complete vanilla closet validation threw " + ex.GetType().Name + ": " + ex.Message; return false; } } public bool TryReadClosetSlots(SyndicateHqStorageDefinition definition, out IReadOnlyList slots, out string reason) { slots = Array.Empty(); try { PlaceableStorageEntity closet = _closets.GetValueOrDefault(definition.Guid); if ((closet == null || IsUnityNull((Object?)(object)closet)) && !TryFindSingleCloset(definition, out closet, out reason)) { return false; } if (IsUnityNull((Object?)(object)closet.StorageEntity) || closet.StorageEntity.ItemSlots == null) { reason = "Closet '" + definition.Name + "' had no readable StorageEntity slots."; return false; } List itemSlots = closet.StorageEntity.ItemSlots; List list = new List(itemSlots.Count); for (int i = 0; i < itemSlots.Count; i++) { ItemSlot val = itemSlots[i]; if (val == null) { reason = $"Closet '{definition.Name}' slot {i} was null."; return false; } ItemInstance itemInstance = val.ItemInstance; if (val.Quantity == 0 || itemInstance == null) { list.Add(new Release1SmallCourtesySlotSnapshot(i, null, null, 0, IsPackaged: false, 0f)); continue; } string text = (((Object)(object)itemInstance.Definition == (Object)null) ? null : ((BaseItemDefinition)itemInstance.Definition).ID); if (string.IsNullOrWhiteSpace(text)) { reason = $"Closet '{definition.Name}' slot {i} carried an item with no definition ID."; return false; } ProductItemInstance val2 = ((Il2CppObjectBase)itemInstance).TryCast(); bool isPackaged = false; string packagingId = "unpackaged"; float num = 0f; if (val2 != null) { PackagingDefinition appliedPackaging = val2.AppliedPackaging; if ((Object)(object)appliedPackaging != (Object)null && !string.IsNullOrWhiteSpace(((BaseItemDefinition)appliedPackaging).ID)) { isPackaged = true; packagingId = ((BaseItemDefinition)appliedPackaging).ID; } else if (!string.IsNullOrWhiteSpace(val2.PackagingID) && !string.Equals(val2.PackagingID, "none", StringComparison.Ordinal)) { isPackaged = true; packagingId = val2.PackagingID; } num = ((BaseItemInstance)val2).GetMonetaryValue(); if (!float.IsFinite(num) || num < 0f) { num = 0f; } } Release1SmallCourtesySlotSnapshot release1SmallCourtesySlotSnapshot = new Release1SmallCourtesySlotSnapshot(i, text, packagingId, val.Quantity, isPackaged, num); release1SmallCourtesySlotSnapshot.Validate(); list.Add(release1SmallCourtesySlotSnapshot); } slots = list; reason = $"Read {list.Count} slots from closet '{definition.Name}'."; return true; } catch (Exception ex) { slots = Array.Empty(); reason = "Closet slot read threw " + ex.GetType().Name + ": " + ex.Message; return false; } } private static bool TryFindSingleCloset(SyndicateHqStorageDefinition definition, out PlaceableStorageEntity closet, out string reason) { closet = null; try { Guid result; PlaceableStorageEntity[] array = ((IEnumerable)Resources.FindObjectsOfTypeAll()).Where((PlaceableStorageEntity storage) => !IsUnityNull((Object?)(object)storage) && !IsUnityNull((Object?)(object)((Component)storage).gameObject) && Guid.TryParse(((object)((BuildableItem)storage).GUID/*cast due to .constrained prefix*/).ToString(), out result) && result == definition.Guid).Take(2).ToArray(); if (array.Length != 1) { reason = $"Exactly one closet did not resolve for {definition.Guid:D}; found {array.Length}."; return false; } closet = array[0]; reason = string.Empty; return true; } catch (Exception ex) { closet = null; reason = $"Closet lookup for {definition.Guid:D} threw {ex.GetType().Name}: {ex.Message}"; return false; } } public Release1SmallCourtesyWorldReadStatus TryReadClosetSlotCashBalance(SyndicateHqStorageDefinition definition, int slotIndex, out float balance, out string reason) { balance = 0f; try { if (!TryResolveClosetSlot(definition, slotIndex, out ItemSlot slot, out reason)) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } ItemInstance val = ((slot.Quantity == 0) ? null : slot.ItemInstance); CashInstance val2 = ((val == null) ? null : ((Il2CppObjectBase)val).TryCast()); if (val2 == null) { reason = $"Closet '{definition.Name}' slot {slotIndex} was empty or did not hold cash."; return Release1SmallCourtesyWorldReadStatus.Unavailable; } balance = val2.Balance; reason = $"Closet '{definition.Name}' slot {slotIndex} cash balance {balance}."; return Release1SmallCourtesyWorldReadStatus.Ready; } catch (Exception ex) { balance = 0f; reason = "Closet cash read threw " + ex.GetType().Name + ": " + ex.Message; return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TrySetClosetSlotLocked(SyndicateHqStorageDefinition definition, int slotIndex, bool locked, out string reason) { try { if (!TryResolveClosetSlot(definition, slotIndex, out ItemSlot slot, out reason)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } (Guid, int) item = (definition.Guid, slotIndex); if (locked && !_ownedClosetSlotLocks.Contains(item) && (slot.IsRemovalLocked || slot.IsAddLocked)) { reason = $"Closet '{definition.Name}' slot {slotIndex} was already locked by the game."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!locked && !_ownedClosetSlotLocks.Contains(item)) { reason = $"Closet '{definition.Name}' slot {slotIndex} was not locked by this mod."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (locked) { _ownedClosetSlotLocks.Add(item); } slot.SetIsRemovalLocked(locked); slot.SetIsAddLocked(locked); reason = $"Closet '{definition.Name}' slot {slotIndex} lock set to {locked}."; if (slot.IsRemovalLocked != locked || slot.IsAddLocked != locked) { return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } if (!locked) { _ownedClosetSlotLocks.Remove(item); } return Release1SmallCourtesyWorldMutationStatus.Succeeded; } catch (Exception ex) { reason = "Closet slot lock threw " + ex.GetType().Name + ": " + ex.Message; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryChangeClosetSlotCashBalance(SyndicateHqStorageDefinition definition, int slotIndex, float amount, out string reason) { (Guid, int) item = (definition.Guid, slotIndex); if (!_ownedClosetSlotLocks.Contains(item)) { reason = $"Closet '{definition.Name}' slot {slotIndex} was not locked by this mod."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!float.IsFinite(amount) || amount >= 0f || amount < -20000f) { reason = $"Closet '{definition.Name}' slot {slotIndex} decrement {amount} was out of bounds."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } if (!TryResolveClosetSlot(definition, slotIndex, out ItemSlot slot, out reason)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } ItemInstance itemInstance = slot.ItemInstance; CashInstance val = ((itemInstance == null) ? null : ((Il2CppObjectBase)itemInstance).TryCast()); if (val == null) { reason = $"Closet '{definition.Name}' slot {slotIndex} did not hold cash."; return Release1SmallCourtesyWorldMutationStatus.Unavailable; } float balance = val.Balance; if (balance + amount < 0f) { reason = $"Closet '{definition.Name}' slot {slotIndex} decrement {amount} would leave a negative balance."; return Release1SmallCourtesyWorldMutationStatus.Rejected; } try { slot.SetIsRemovalLocked(false); if (slot.IsRemovalLocked) { reason = $"Closet '{definition.Name}' slot {slotIndex} would not unlock for the change."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } val.ChangeBalance(amount); } finally { slot.SetIsRemovalLocked(true); } if (!slot.IsRemovalLocked) { reason = $"Closet '{definition.Name}' slot {slotIndex} would not relock after the change."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } float num = balance + amount; ItemInstance itemInstance2 = slot.ItemInstance; if (num <= 0f) { if (itemInstance2 == null || slot.Quantity == 0) { reason = $"Closet '{definition.Name}' slot {slotIndex} changed by {amount} from {balance}."; return Release1SmallCourtesyWorldMutationStatus.Succeeded; } CashInstance val2 = ((Il2CppObjectBase)itemInstance2).TryCast(); if (val2 != null && MathF.Abs(val2.Balance) <= 0.01f) { reason = $"Closet '{definition.Name}' slot {slotIndex} changed by {amount} from {balance}."; return Release1SmallCourtesyWorldMutationStatus.Succeeded; } reason = $"Closet '{definition.Name}' slot {slotIndex} did not settle at the expected zero balance after the change."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } CashInstance val3 = ((itemInstance2 == null) ? null : ((Il2CppObjectBase)itemInstance2).TryCast()); if (val3 != null && MathF.Abs(val3.Balance - num) <= 0.01f) { reason = $"Closet '{definition.Name}' slot {slotIndex} changed by {amount} from {balance}."; return Release1SmallCourtesyWorldMutationStatus.Succeeded; } reason = $"Closet '{definition.Name}' slot {slotIndex} did not settle at the expected balance after the change."; return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } public void ReleaseOwnedClosetSlotLocks() { (Guid, int)[] array = _ownedClosetSlotLocks.ToArray(); for (int i = 0; i < array.Length; i++) { (Guid, int) tuple = array[i]; Guid guid = tuple.Item1; int item = tuple.Item2; SyndicateHqStorageDefinition syndicateHqStorageDefinition = SyndicateHqStorageContract.Definitions.FirstOrDefault((SyndicateHqStorageDefinition candidate) => candidate.Guid == guid); if ((object)syndicateHqStorageDefinition != null && TryResolveClosetSlot(syndicateHqStorageDefinition, item, out ItemSlot slot, out string _)) { try { slot.SetIsRemovalLocked(false); slot.SetIsAddLocked(false); } catch { } } _ownedClosetSlotLocks.Remove((guid, item)); } } private bool TryResolveClosetSlot(SyndicateHqStorageDefinition definition, int slotIndex, out ItemSlot slot, out string reason) { slot = null; PlaceableStorageEntity closet = _closets.GetValueOrDefault(definition.Guid); if ((closet == null || IsUnityNull((Object?)(object)closet)) && !TryFindSingleCloset(definition, out closet, out reason)) { return false; } if (IsUnityNull((Object?)(object)closet.StorageEntity) || closet.StorageEntity.ItemSlots == null || slotIndex < 0 || slotIndex >= closet.StorageEntity.ItemSlots.Count || closet.StorageEntity.ItemSlots[slotIndex] == null) { reason = $"Closet '{definition.Name}' slot {slotIndex} was out of range, null, or unreadable."; return false; } slot = closet.StorageEntity.ItemSlots[slotIndex]; reason = string.Empty; return true; } public bool TryPlace(object entity, SyndicateHqVector3 worldPosition, float yawDegrees, out string reason) { //IL_0055: 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_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_00c9: 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_00db: Unknown result type (might be due to invalid IL or missing references) //IL_00e0: 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_00f3: Unknown result type (might be due to invalid IL or missing references) PlaceableStorageEntity val = (PlaceableStorageEntity)((entity is PlaceableStorageEntity) ? entity : null); if (val == null || IsUnityNull((Object?)(object)val) || !worldPosition.IsFinite || !float.IsFinite(yawDegrees)) { reason = "The standard vanilla closet placement inputs were invalid."; return false; } try { Vector3 val2 = default(Vector3); ((Vector3)(ref val2))..ctor(worldPosition.X, worldPosition.Y, worldPosition.Z); Quaternion val3 = Quaternion.Euler(0f, yawDegrees, 0f); if (!IsUnityNull((Object?)(object)_grid)) { ((Component)_grid).transform.SetParent((Transform)null, true); ((Component)_grid).gameObject.SetActive(true); } if (Guid.TryParse(((object)((BuildableItem)val).GUID/*cast due to .constrained prefix*/).ToString(), out var result)) { SyndicateHqStorageCullingGuard.SetVisible(result, visible: true); } ((BuildableItem)val).SetCulled(false); ((Component)val).gameObject.SetActive(true); ((Component)val).transform.SetPositionAndRotation(val2, val3); Physics.SyncTransforms(); float num = Vector3.Distance(((Component)val).transform.position, val2); float num2 = Quaternion.Angle(((Component)val).transform.rotation, val3); if (!((Component)val).gameObject.activeInHierarchy || num > 0.05f || num2 > 0.5f) { reason = $"The standard vanilla closet did not reach its visible HQ transform (active={((Component)val).gameObject.activeInHierarchy}, positionError={num:F3}, rotationError={num2:F3})."; return false; } reason = "The standard vanilla closet is active at its verified HQ world transform."; return true; } catch (Exception ex) { reason = "Standard vanilla closet activation threw " + ex.GetType().Name + ": " + ex.Message; return false; } } public void SetInteractionEnabled(object entity, bool enabled) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) PlaceableStorageEntity closet = (PlaceableStorageEntity)((entity is PlaceableStorageEntity) ? entity : null); if (closet == null || IsUnityNull((Object?)(object)closet) || IsUnityNull((Object?)(object)((Component)closet).gameObject)) { return; } Guid guid; bool hasGuid = Guid.TryParse(((object)((BuildableItem)closet).GUID/*cast due to .constrained prefix*/).ToString(), out guid); SyndicateHqStorageCullingGuard.ApplyVisibility(enabled, delegate(bool visible) { if (hasGuid) { SyndicateHqStorageCullingGuard.SetVisible(guid, visible); } }, delegate { ((BuildableItem)closet).SetCulled(false); }, delegate(bool active) { ((Component)closet).gameObject.SetActive(active); }); } public bool TryCleanup(object entity, out string reason) { //IL_001d: 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) PlaceableStorageEntity val = (PlaceableStorageEntity)((entity is PlaceableStorageEntity) ? entity : null); if (val == null || IsUnityNull((Object?)(object)val)) { reason = "The native closet was already unavailable."; return true; } try { if (Guid.TryParse(((object)((BuildableItem)val).GUID/*cast due to .constrained prefix*/).ToString(), out var result)) { SyndicateHqStorageCullingGuard.SetVisible(result, visible: false); _closets.Remove(result); } ((BuildableItem)val).Destroy_Server(); reason = "Rolled back the newly-created native closet."; return true; } catch (Exception ex) { reason = "Native closet rollback threw " + ex.GetType().Name + ": " + ex.Message; return false; } } private static Vector2 CoordinateFor(SyndicateHqStorageDefinition definition) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) return new Vector2((float)definition.GridOriginX, (float)definition.GridOriginY); } private static int NormalizeGridRotation(float yawDegrees) { int num = (int)MathF.Round(yawDegrees / 90f) % 4; if (num >= 0) { return num; } return num + 4; } private void PrepareCompleteCloset(PlaceableStorageEntity closet, SyndicateHqStorageDefinition definition, bool enabled) { SyndicateHqStorageCullingGuard.SetVisible(definition.Guid, enabled); ((Object)((Component)closet).gameObject).name = definition.Name; ((BuildableItem)closet).SetCulled(false); ((Component)closet).gameObject.SetActive(enabled); } private static void RollbackCreated(GridItem? created) { if (IsUnityNull((Object?)(object)created)) { return; } try { ((BuildableItem)created).Destroy_Server(); } catch { } } private void RetainGrid(Grid grid) { _grid = grid; _gridProxy = ((Component)grid).gameObject; _parentProperty = grid.ParentProperty; } private static Grid? FindGridByGuid(string guid) { return ((IEnumerable)Object.FindObjectsOfType(true)).FirstOrDefault((Func)((Grid grid) => !IsUnityNull((Object?)(object)grid) && string.Equals(((object)grid.GUID/*cast due to .constrained prefix*/).ToString(), guid, StringComparison.OrdinalIgnoreCase))); } private static Grid? FindLargestOwnedGrid(out int tileCount) { tileCount = 0; Grid result = null; foreach (Grid item in Object.FindObjectsOfType(true)) { Property val = ((item != null) ? item.ParentProperty : null); if (!IsUnityNull((Object?)(object)item) && !IsUnityNull((Object?)(object)((item != null) ? ((Component)item).gameObject : null)) && !IsUnityNull((Object?)(object)val) && val.IsOwned) { int length = ((Component)item).GetComponentsInChildren(true).Length; if (length > tileCount) { result = item; tileCount = length; } } } return result; } private bool IsPreparedGrid(Grid? grid) { if (HasReservedIdentityAndOwnedParent(grid)) { return ClassifyGrid(grid) == SyndicateHqStorageGridShape.Current; } return false; } private bool IsLegacyGrid(Grid? grid) { if (HasReservedIdentityAndOwnedParent(grid)) { return ClassifyGrid(grid) == SyndicateHqStorageGridShape.LegacyTwoCloset; } return false; } private static bool HasReservedIdentityAndOwnedParent(Grid? grid) { //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) if (!IsUnityNull((Object?)(object)grid) && !IsUnityNull((Object?)(object)((grid != null) ? ((Component)grid).gameObject : null)) && !IsUnityNull((Object?)(object)((grid != null) ? grid.ParentProperty : null)) && grid.ParentProperty.IsOwned) { return string.Equals(((object)grid.GUID/*cast due to .constrained prefix*/).ToString(), "7ce4e626-4438-48c4-a88a-c1b47f99d66f", StringComparison.OrdinalIgnoreCase); } return false; } private static SyndicateHqStorageGridShape ClassifyGrid(Grid? grid) { if (IsUnityNull((Object?)(object)grid)) { return SyndicateHqStorageGridShape.Invalid; } return SyndicateHqStorageGridContract.Classify(from tile in (IEnumerable)((Component)grid).GetComponentsInChildren(true) where !IsUnityNull((Object?)(object)tile) select new SyndicateHqStorageGridCoordinate(tile.x, tile.y)); } private bool TryUpgradeLegacyGrid(Grid grid, out string reason) { GameObject val = null; try { Property parentProperty = grid.ParentProperty; Tile val2 = ((IEnumerable)((Component)grid).GetComponentsInChildren(true)).FirstOrDefault((Func)((Tile tile) => !IsUnityNull((Object?)(object)tile) && !IsUnityNull((Object?)(object)((Component)tile).gameObject))); if (IsUnityNull((Object?)(object)parentProperty) || IsUnityNull((Object?)(object)val2)) { reason = "The exact legacy HQ Grid did not retain its owned Property or source Tile."; return false; } val = Object.Instantiate(((Component)val2).gameObject); ((Object)val).name = "OC_SyndicateHQ_StorageGrid_LegacyTileTemplate"; val.SetActive(false); Tile component = val.GetComponent(); if (IsUnityNull((Object?)(object)component)) { throw new InvalidOperationException("The legacy HQ Grid tile template lost its Tile component."); } ((Component)grid).gameObject.SetActive(false); ReplaceTilesWithDeterministicFootprint(((Component)grid).gameObject, grid, component); DisableProxyVisualsAndColliders(((Component)grid).gameObject); PositionGrid(((Component)grid).transform); ((Component)grid).gameObject.SetActive(true); Tile[] tiles = ((IEnumerable)((Component)grid).GetComponentsInChildren(true)).Where((Tile tile) => !IsUnityNull((Object?)(object)tile)).ToArray(); RebuildNativeTopology(grid, tiles); RegisterPropertyGrid(parentProperty, grid); if (!IsPreparedGrid(grid)) { throw new InvalidOperationException("The expanded native HQ Grid failed its GUID, Property, or tile validation."); } Physics.SyncTransforms(); reason = $"Expanded the exact legacy 12x14 HQ Grid in place to {16}x{32}; existing closet GUIDs remain unchanged."; return true; } catch (Exception ex) { reason = "Legacy native HQ Grid expansion threw " + ex.GetType().Name + ": " + ex.Message; return false; } finally { if (!IsUnityNull((Object?)(object)val)) { Object.DestroyImmediate((Object)(object)val); } } } private static void ReplaceTilesWithDeterministicFootprint(GameObject root, Grid grid, Tile sourceTile) { //IL_0075: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Expected O, but got Unknown //IL_0126: 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) Tile[] array = (from tile in (IEnumerable)root.GetComponentsInChildren(true) where !IsUnityNull((Object?)(object)tile) && !IsUnityNull((Object?)(object)((Component)tile).gameObject) orderby HierarchyDepth(((Component)tile).transform) descending select tile).ToArray(); for (int num = 0; num < array.Length; num++) { Object.DestroyImmediate((Object)(object)((Component)array[num]).gameObject); } GameObject val = new GameObject("OC_SyndicateHQ_StorageTiles"); val.transform.SetParent(root.transform, false); for (int num2 = 0; num2 < 16; num2++) { for (int num3 = 0; num3 < 32; num3++) { GameObject obj = Object.Instantiate(((Component)sourceTile).gameObject, val.transform, false); ((Object)obj).name = $"Tile_{num2}_{num3}"; SyndicateHqStorageDefinition syndicateHqStorageDefinition = SyndicateHqStorageContract.Definitions[0]; obj.transform.localPosition = new Vector3((float)(num2 - syndicateHqStorageDefinition.GridOriginX) * Grid.TileSize, 0f, (float)(num3 - syndicateHqStorageDefinition.GridOriginY) * Grid.TileSize); obj.transform.localRotation = Quaternion.identity; Tile obj2 = obj.GetComponent() ?? throw new InvalidOperationException("A deterministic HQ tile clone lost its Tile component."); obj2.x = num2; obj2.y = num3; obj2.OwnerGrid = grid; ClearMember(obj2, "BuildableOccupants"); ClearMember(obj2, "OccupantTiles"); } } } private static void PositionGrid(Transform grid) { //IL_0012: 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_0031: 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_0047: Unknown result type (might be due to invalid IL or missing references) SyndicateHqVector3 localPosition = SyndicateHqStorageContract.Definitions[0].LocalPosition; grid.position = PocketRoot + new Vector3(localPosition.X, localPosition.Y, localPosition.Z); grid.rotation = Quaternion.identity; grid.localScale = Vector3.one; } private static void DisableProxyVisualsAndColliders(GameObject root) { foreach (Renderer componentsInChild in root.GetComponentsInChildren(true)) { if (!IsUnityNull((Object?)(object)componentsInChild)) { componentsInChild.enabled = false; } } foreach (Collider componentsInChild2 in root.GetComponentsInChildren(true)) { if (!IsUnityNull((Object?)(object)componentsInChild2)) { componentsInChild2.enabled = false; } } } private static void RebuildNativeTopology(Grid grid, Tile[] tiles) { grid.Tiles.Clear(); grid.CoordinateTilePairs.Clear(); foreach (Tile val in tiles) { val.OwnerGrid = grid; grid.RegisterTile(val); } if (!Invoke(GetMember(grid, "_coordinateToTile"), "Clear") || !Invoke(grid, "ProcessCoordinateDataPairs") || !Invoke(grid, "SetGridSize")) { throw new InvalidOperationException("The deterministic native HQ Grid topology could not be rebuilt."); } if (grid.Tiles.Count != 512) { throw new InvalidOperationException($"The native HQ Grid retained {grid.Tiles.Count} tiles instead of {512}."); } } private static void RegisterPropertyGrid(Property property, Grid grid) { if (property.Grids == null) { throw new InvalidOperationException("The owned parent Property did not expose its Grid registry."); } if (!property.Grids.Contains(grid)) { property.Grids.Add(grid); } } private static int HierarchyDepth(Transform transform) { int num = 0; Transform val = transform; while (!IsUnityNull((Object?)(object)val)) { num++; val = val.parent; } return num; } private static object? GetMember(object target, string name) { Type type = target.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)property == null) { return type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)?.GetValue(target); } return property.GetValue(target); } private static bool TrySetMember(object target, string name, object value) { try { Type type = target.GetType(); PropertyInfo property = type.GetProperty(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)property != null && property.CanWrite) { property.SetValue(target, value); return true; } FieldInfo field = type.GetField(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if ((object)field == null) { return false; } field.SetValue(target, value); return true; } catch { return false; } } private static bool ClearMember(object target, string name) { object member = GetMember(target, name); if (member != null) { return Invoke(member, "Clear"); } return false; } private static bool Invoke(object? target, string name) { if (target == null) { return false; } try { MethodInfo method = target.GetType().GetMethod(name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); if ((object)method == null) { return false; } method.Invoke(target, null); return true; } catch { return false; } } private static bool IsUnityNull(object? value) { if (value != null) { return value == null; } return true; } private static bool IsUnityNull(Object? value) { return value == (Object)null; } } public interface ISyndicateHqStorageRuntime : IDisposable { SyndicateHqStorageResult CurrentResult { get; } SyndicateHqStorageResult BeginLoad(SyndicateHqStorageContext context); SyndicateHqStorageResult PrepareAtVanillaLoadBoundary(); SyndicateHqStorageResult CompleteVanillaLoadBoundary(); SyndicateHqStorageResult PlaceAtPocket(SyndicateHqVector3 pocketRoot); Release1HoldRoomSnapshot ReadHoldRoom(); Release1SmallCourtesyWorldReadStatus TryReadClosetSlotCashBalance(string closetGuid, int slotIndex, out float balance); Release1SmallCourtesyWorldMutationStatus TrySetClosetSlotLocked(string closetGuid, int slotIndex, bool locked); Release1SmallCourtesyWorldMutationStatus TryChangeClosetSlotCashBalance(string closetGuid, int slotIndex, float amount); void DisableInteractions(); void MarkTeardown(); } public sealed class SyndicateHqNativeStorageRuntime : ISyndicateHqStorageRuntime, IDisposable { private sealed record RetainedStorage(SyndicateHqStorageDefinition Definition, object Entity, bool CreatedThisAttempt); private readonly ISyndicateHqNativeStorageBoundary _boundary; private readonly Action _log; private readonly Action _receiptLog; private readonly List _retained = new List(); private SyndicateHqStorageContext? _boundContext; private object? _preparedGrid; private bool _admitted; private bool _creationAttempted; private bool _gridBoundaryHandled; private bool _completionBoundaryHandled; private bool _disposed; private bool _interactionsEnabled = true; private bool _tearingDown; public SyndicateHqStorageResult CurrentResult { get; private set; } public SyndicateHqNativeStorageRuntime(ISyndicateHqNativeStorageBoundary boundary, Action? log = null, Action? receiptLog = null) { _boundary = boundary ?? throw new ArgumentNullException("boundary"); _log = log ?? ((Action)delegate { }); _receiptLog = receiptLog ?? _log; CurrentResult = new SyndicateHqStorageResult(SyndicateHqStorageStatus.NotPrepared, "Native HQ storage has not been prepared."); } public SyndicateHqStorageResult BeginLoad(SyndicateHqStorageContext context) { if (_disposed) { return Set(SyndicateHqStorageStatus.Disposed, "Native HQ storage was disposed."); } SyndicateHqStorageResult syndicateHqStorageResult = SyndicateHqStorageAdmission.Evaluate(context); if (!syndicateHqStorageResult.IsReady) { _admitted = false; DisableInteractions(); return Set(syndicateHqStorageResult.Status, syndicateHqStorageResult.Reason); } if ((object)_boundContext != null && (!PathsEqual(_boundContext.BoundSaveFolder, context.BoundSaveFolder) || !string.Equals(_boundContext.CanonicalPlayerId, context.CanonicalPlayerId, StringComparison.Ordinal))) { _admitted = false; DisableInteractions(); return Set(SyndicateHqStorageStatus.SaveBindingChanged, "The process was already bound to a different save folder."); } if ((object)_boundContext == null) { _boundContext = context; } DisableInteractions(); _retained.Clear(); _admitted = true; _creationAttempted = false; _gridBoundaryHandled = false; _completionBoundaryHandled = false; _preparedGrid = null; return Set(SyndicateHqStorageStatus.NotPrepared, "Native HQ storage is admitted and awaiting the vanilla storage-load boundary."); } public SyndicateHqStorageResult PrepareAtVanillaLoadBoundary() { if (_disposed) { return Set(SyndicateHqStorageStatus.Disposed, "Native HQ storage was disposed."); } if (!_admitted || (object)_boundContext == null) { return CurrentResult; } if (_gridBoundaryHandled) { return CurrentResult; } _gridBoundaryHandled = true; try { if (!_boundary.TryPrepareGrid(out _preparedGrid, out string reason)) { return Set(SyndicateHqStorageStatus.GridUnavailable, reason); } return Set(SyndicateHqStorageStatus.NotPrepared, "The native HQ Grid is ready and awaiting vanilla closet restoration."); } catch (Exception ex) { return Set(SyndicateHqStorageStatus.GridUnavailable, "Native Grid preparation threw " + ex.GetType().Name + ": " + ex.Message); } } public SyndicateHqStorageResult CompleteVanillaLoadBoundary() { if (_disposed) { return Set(SyndicateHqStorageStatus.Disposed, "Native HQ storage was disposed."); } if (!_admitted || (object)_boundContext == null || !_gridBoundaryHandled || _preparedGrid == null) { return CurrentResult; } if (_completionBoundaryHandled) { return CurrentResult; } _completionBoundaryHandled = true; if (_creationAttempted) { return CurrentResult; } _creationAttempted = true; try { _boundary.EvictRetainedClosetsForLoad(); foreach (SyndicateHqStorageDefinition definition in SyndicateHqStorageContract.Definitions) { bool createdThisAttempt = false; object entity; string reason; switch (_boundary.FindRestoredCloset(definition, out entity, out reason)) { case SyndicateHqNativeStorageLookupStatus.IdentityConflict: return FailCreated(SyndicateHqStorageStatus.IdentityConflict, reason); case SyndicateHqNativeStorageLookupStatus.Missing: { if (!_boundary.TryCreate(_preparedGrid, definition, out entity, out string reason2)) { return FailCreated(SyndicateHqStorageStatus.SpawnUnavailable, reason2); } createdThisAttempt = true; break; } } _retained.Add(new RetainedStorage(definition, entity, createdThisAttempt)); if (!_boundary.TryValidate(entity, definition, out string reason3)) { return FailCreated(SyndicateHqStorageStatus.ValidationFailed, reason3); } } _interactionsEnabled = true; return Set(SyndicateHqStorageStatus.Ready, "Nine complete vanilla 20-slot HQ closets were restored or created."); } catch (Exception ex) { return FailCreated(SyndicateHqStorageStatus.SpawnUnavailable, "Native storage preparation threw " + ex.GetType().Name + ": " + ex.Message); } } public SyndicateHqStorageResult PlaceAtPocket(SyndicateHqVector3 pocketRoot) { if (_disposed) { return Set(SyndicateHqStorageStatus.Disposed, "Native HQ storage was disposed."); } if (!pocketRoot.IsFinite || CurrentResult.Status != SyndicateHqStorageStatus.Ready || _retained.Count != SyndicateHqStorageContract.Definitions.Count) { DisableInteractions(); if (CurrentResult.Status != SyndicateHqStorageStatus.Ready) { return CurrentResult; } return Set(SyndicateHqStorageStatus.ValidationFailed, "Native HQ storage placement inputs were invalid."); } try { foreach (RetainedStorage item in _retained) { SyndicateHqVector3 worldPosition = pocketRoot + item.Definition.LocalPosition; if (!_boundary.TryPlace(item.Entity, worldPosition, item.Definition.YawDegrees, out string reason)) { DisableInteractions(); return Set(SyndicateHqStorageStatus.ValidationFailed, reason); } } foreach (RetainedStorage item2 in _retained) { _boundary.SetInteractionEnabled(item2.Entity, enabled: true); } _interactionsEnabled = true; return Set(SyndicateHqStorageStatus.Ready, "Nine native HQ storage interactions were placed and enabled."); } catch (Exception ex) { DisableInteractions(); return Set(SyndicateHqStorageStatus.ValidationFailed, "Native storage placement threw " + ex.GetType().Name + ": " + ex.Message); } } public Release1HoldRoomSnapshot ReadHoldRoom() { if (_disposed) { return Release1HoldRoomSnapshot.Unavailable(); } if (CurrentResult.Status != SyndicateHqStorageStatus.Ready || _retained.Count != SyndicateHqStorageContract.Definitions.Count) { return Release1HoldRoomSnapshot.NotReady(); } List list = new List(_retained.Count); foreach (RetainedStorage item in _retained.OrderBy((RetainedStorage item) => item.Definition.Guid.ToString("D"), StringComparer.Ordinal)) { IReadOnlyList slots; try { if (!_boundary.TryReadClosetSlots(item.Definition, out slots, out string reason)) { _log("Native HQ storage hold-room read held: " + reason); return Release1HoldRoomSnapshot.NotReady(); } } catch (Exception ex) { _log("Native HQ storage hold-room read threw " + ex.GetType().Name + ": " + ex.Message); return Release1HoldRoomSnapshot.Unavailable(); } Release1HoldRoomClosetSnapshot release1HoldRoomClosetSnapshot = new Release1HoldRoomClosetSnapshot(item.Definition.Guid.ToString("D"), slots.Count, slots); try { release1HoldRoomClosetSnapshot.Validate(); } catch (ArgumentException ex2) { _log("Native HQ storage hold-room read held: " + ex2.Message); return Release1HoldRoomSnapshot.NotReady(); } list.Add(release1HoldRoomClosetSnapshot); } Release1HoldRoomSnapshot release1HoldRoomSnapshot = new Release1HoldRoomSnapshot(Release1HoldRoomReadiness.Ready, list); try { release1HoldRoomSnapshot.Validate(); return release1HoldRoomSnapshot; } catch (ArgumentException) { return Release1HoldRoomSnapshot.NotReady(); } } public Release1SmallCourtesyWorldReadStatus TryReadClosetSlotCashBalance(string closetGuid, int slotIndex, out float balance) { balance = 0f; if (!TryResolveRetainedDefinition(closetGuid, out SyndicateHqStorageDefinition definition)) { return Release1SmallCourtesyWorldReadStatus.Unavailable; } try { float balance2; string reason; Release1SmallCourtesyWorldReadStatus release1SmallCourtesyWorldReadStatus = _boundary.TryReadClosetSlotCashBalance(definition, slotIndex, out balance2, out reason); if (release1SmallCourtesyWorldReadStatus != Release1SmallCourtesyWorldReadStatus.Ready) { _log("Native HQ storage closet cash read held: " + reason); return release1SmallCourtesyWorldReadStatus; } balance = balance2; return Release1SmallCourtesyWorldReadStatus.Ready; } catch (Exception ex) { _log("Native HQ storage closet cash read threw " + ex.GetType().Name + ": " + ex.Message); return Release1SmallCourtesyWorldReadStatus.Faulted; } } public Release1SmallCourtesyWorldMutationStatus TrySetClosetSlotLocked(string closetGuid, int slotIndex, bool locked) { if (!TryResolveRetainedDefinition(closetGuid, out SyndicateHqStorageDefinition definition)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } try { string reason; Release1SmallCourtesyWorldMutationStatus num = _boundary.TrySetClosetSlotLocked(definition, slotIndex, locked, out reason); if (num != Release1SmallCourtesyWorldMutationStatus.Succeeded) { _log("Native HQ storage closet lock held: " + reason); } return num; } catch (Exception ex) { _log("Native HQ storage closet lock threw " + ex.GetType().Name + ": " + ex.Message); return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } public Release1SmallCourtesyWorldMutationStatus TryChangeClosetSlotCashBalance(string closetGuid, int slotIndex, float amount) { if (!TryResolveRetainedDefinition(closetGuid, out SyndicateHqStorageDefinition definition)) { return Release1SmallCourtesyWorldMutationStatus.Unavailable; } try { string reason; Release1SmallCourtesyWorldMutationStatus num = _boundary.TryChangeClosetSlotCashBalance(definition, slotIndex, amount, out reason); if (num != Release1SmallCourtesyWorldMutationStatus.Succeeded) { _log("Native HQ storage closet cash change held: " + reason); } return num; } catch (Exception ex) { _log("Native HQ storage closet cash change threw " + ex.GetType().Name + ": " + ex.Message); return Release1SmallCourtesyWorldMutationStatus.Ambiguous; } } private bool TryResolveRetainedDefinition(string closetGuid, out SyndicateHqStorageDefinition definition) { definition = null; if (_disposed || CurrentResult.Status != SyndicateHqStorageStatus.Ready || _retained.Count != SyndicateHqStorageContract.Definitions.Count) { return false; } RetainedStorage retainedStorage = _retained.FirstOrDefault((RetainedStorage item) => string.Equals(item.Definition.Guid.ToString("D"), closetGuid, StringComparison.OrdinalIgnoreCase)); if ((object)retainedStorage == null) { return false; } definition = retainedStorage.Definition; return true; } public void DisableInteractions() { if (_tearingDown) { if (_interactionsEnabled) { _receiptLog($"Native HQ storage interactions left to the game during teardown; count={_retained.Count}"); _interactionsEnabled = false; } } else { if (!_interactionsEnabled) { return; } foreach (RetainedStorage item in _retained) { try { _boundary.SetInteractionEnabled(item.Entity, enabled: false); } catch (Exception ex) { _log("Native HQ storage interaction disable failed: " + ex.GetType().Name + ": " + ex.Message); } } _interactionsEnabled = false; } } public void MarkTeardown() { _tearingDown = true; } public void Dispose() { if (!_disposed) { try { _boundary.ReleaseOwnedClosetSlotLocks(); } catch { } DisableInteractions(); _disposed = true; _admitted = false; _retained.Clear(); _preparedGrid = null; Set(SyndicateHqStorageStatus.Disposed, "Native HQ storage was disposed."); } } private SyndicateHqStorageResult FailCreated(SyndicateHqStorageStatus status, string reason) { List list = new List(); foreach (RetainedStorage item in _retained.Where((RetainedStorage item) => item.CreatedThisAttempt)) { try { if (!_boundary.TryCleanup(item.Entity, out string reason2)) { list.Add(reason2); } } catch (Exception ex) { list.Add(ex.GetType().Name + ": " + ex.Message); } } _retained.Clear(); if (list.Count > 0) { reason = reason + " Cleanup remained incomplete: " + string.Join(" | ", list); } return Set(status, reason); } private SyndicateHqStorageResult Set(SyndicateHqStorageStatus status, string reason) { CurrentResult = new SyndicateHqStorageResult(status, reason); if (status != SyndicateHqStorageStatus.Ready && status != SyndicateHqStorageStatus.NotPrepared) { ((status == SyndicateHqStorageStatus.Disposed) ? _receiptLog : _log)($"Native HQ storage: {status}: {reason}"); } return CurrentResult; } private static bool PathsEqual(string left, string right) { try { string a = Path.TrimEndingDirectorySeparator(Path.GetFullPath(left)); string b = Path.TrimEndingDirectorySeparator(Path.GetFullPath(right)); return string.Equals(a, b, StringComparison.OrdinalIgnoreCase); } catch { return false; } } } public sealed class SyndicateHqRuntimeComposition { private readonly SyndicateHqRuntimeService _service; private readonly Action _beginLoad; private bool _pendingLoadRecovery; private bool _deinitializing; public SyndicateHqRuntimeService Service => _service; public bool IsPendingCleanup { get { if (_deinitializing) { return !_service.IsDisposed; } return false; } } public SyndicateHqRuntimeComposition(SyndicateHqRuntimeService service, Action beginLoad) { _service = service ?? throw new ArgumentNullException("service"); _beginLoad = beginLoad ?? throw new ArgumentNullException("beginLoad"); } public bool OnPreLoad() { if (_deinitializing) { return false; } bool num = _service.OnPreLoad(); if (num) { _pendingLoadRecovery = false; _beginLoad(); return num; } _pendingLoadRecovery = true; return num; } public bool PrepareLoadComplete() { if (_deinitializing) { return false; } if (!_pendingLoadRecovery) { return true; } if (!_service.TryTeardown()) { return false; } _beginLoad(); _pendingLoadRecovery = false; return true; } public bool OnLoadComplete() { if (!PrepareLoadComplete()) { return false; } _service.EvictRetainedDoorIdentityForLoad(); return _service.OnLoadComplete(); } public bool OnPreSceneChange() { if (!_deinitializing) { return _service.OnPreSceneChange(); } return false; } public bool OnSaveStart() { if (!_deinitializing) { return _service.OnSaveStart(); } return false; } public bool OnSaveComplete() { if (!_deinitializing) { return _service.OnSaveComplete(); } return false; } public void Update() { if (!_deinitializing) { _service.Update(); } } public void OnGUI() { if (!_deinitializing) { _service.OnGUI(); } } public bool TryDispose() { _deinitializing = true; _service.Dispose(); if (!_service.IsDisposed) { return false; } _pendingLoadRecovery = false; return true; } } public interface ISyndicateHqPlayerTransport { bool TryCapture(SyndicateHqDoorFingerprint door, Release1StoryHostContextSnapshot context, out SyndicateHqExteriorReturnState state); bool TryTeleport(SyndicateHqVector3 position); bool TryReadPosition(out SyndicateHqVector3 position); } public interface ISyndicateHqUnlockAuthority { SyndicateHqUnlockDecision Evaluate(); } public interface ISyndicateHqInterior { bool IsCreated { get; } bool IsDestroyPending { get; } bool TryEnsure(SyndicateHqVector3 exteriorAnchor); bool TryGetMarker(string name, out SyndicateHqVector3 position); void Destroy(); void Dispose(); } public sealed class Release1StorySyndicateHqUnlockAuthority : ISyndicateHqUnlockAuthority { private readonly IRelease1StoryHostContext _context; private readonly Release1StoryRuntimeService _story; public Release1StorySyndicateHqUnlockAuthority(IRelease1StoryHostContext context, Release1StoryRuntimeService story) { _context = context; _story = story; } public SyndicateHqUnlockDecision Evaluate() { try { Release1StoryHostContextSnapshot snapshot; return SyndicateHqUnlockAdmission.Evaluate(_context.TryRead(out snapshot), snapshot, _story.State); } catch (Exception ex) { return SyndicateHqUnlockDecision.Locked("Unlock authority failed closed: " + ex.Message); } } } public sealed class SyndicateHqRuntimeService : IDisposable { private readonly IRelease1StoryHostContext _context; private readonly ISyndicateHqUnlockAuthority _authority; private readonly ISyndicateHqDoorSource _doorObserver; private readonly ISyndicateHqInterior _interior; private readonly ISyndicateHqPlayerTransport _transport; private readonly Action _log; private readonly Func _now; private readonly SyndicateHqEntryStateMachine _machine = new SyndicateHqEntryStateMachine(); private SyndicateHqExteriorReturnState? _returnState; private SyndicateHqPrompt _prompt = new SyndicateHqPrompt(SyndicateHqPromptKind.None, string.Empty, string.Empty); private SyndicateHqDoorFingerprint? _promptFingerprint; private Release1StoryHostContextSnapshot? _promptContext; private long _promptGeneration; private float _promptExpiresAt; private bool _runtimePrepared; private bool _teardownBlocked; private bool _recoveryRequiresFreshAccessPoint; private bool _returnedForTeardown; private bool _exitPromptArmed; private bool _disposed; public SyndicateHqEntryState State => _machine.State; public SyndicateHqPrompt Prompt => _prompt; public bool IsInside => _machine.State == SyndicateHqEntryState.Inside; public bool IsTeardownBlocked => _teardownBlocked; public bool IsDisposed => _disposed; public SyndicateHqRuntimeService(IRelease1StoryHostContext context, ISyndicateHqUnlockAuthority authority, ISyndicateHqDoorSource doorObserver, ISyndicateHqInterior interior, ISyndicateHqPlayerTransport transport, Action? log = null, Func? now = null) { _context = context ?? throw new ArgumentNullException("context"); _authority = authority ?? throw new ArgumentNullException("authority"); _doorObserver = doorObserver ?? throw new ArgumentNullException("doorObserver"); _interior = interior ?? throw new ArgumentNullException("interior"); _transport = transport ?? throw new ArgumentNullException("transport"); _log = log ?? ((Action)delegate { }); _now = now ?? ((Func)(() => Time.unscaledTime)); } public void EvictRetainedDoorIdentityForLoad() { _doorObserver.EvictRetainedForLoad(); } public bool OnLoadComplete() { if (_disposed) { return false; } if (_teardownBlocked && !TryTeardown()) { return false; } if (_runtimePrepared) { return true; } try { if (!_doorObserver.TryAttach()) { _machine.Fault(); return false; } SyndicateHqDoorFingerprint currentFingerprint = _doorObserver.CurrentFingerprint; if ((object)currentFingerprint == null || !_interior.TryEnsure(currentFingerprint.Position)) { FailActivationCleanup(); return false; } _machine.MarkReady(); _runtimePrepared = true; _teardownBlocked = false; return true; } catch (Exception ex) { _log("Syndicate HQ load activation failed: " + ex.Message); FailActivationCleanup(); return false; } } public bool OnPreLoad() { return TeardownAtBoundary(); } public bool OnPreSceneChange() { return TeardownAtBoundary(); } public bool OnSaveStart() { return TeardownAtBoundary(); } public bool OnSaveComplete() { if (_disposed) { return false; } if (_teardownBlocked && !TryTeardown()) { return false; } if (!_runtimePrepared) { return OnLoadComplete(); } return true; } public void Update() { if (_disposed) { return; } try { if (_prompt.Kind != SyndicateHqPromptKind.None && _now() >= _promptExpiresAt) { ClearPrompt(); } if (IsInside && _interior.TryGetMarker("HQ_EXIT", out var position) && _transport.TryReadPosition(out var position2)) { float num = position2.DistanceTo(position); if (num > 1.5f) { _exitPromptArmed = true; } bool flag = _exitPromptArmed && num <= 1.5f; if (flag && _context.TryRead(out var snapshot) == Release1StoryHostContextReadStatus.Ready) { ShowPrompt(new SyndicateHqPrompt(SyndicateHqPromptKind.Exit, "Leave Syndicate HQ.", "Exit Syndicate HQ"), 2f, _doorObserver.Generation, _doorObserver.CurrentFingerprint, snapshot); } else if (!flag && _prompt.Kind == SyndicateHqPromptKind.Exit) { ClearPrompt(); } } SyndicateHqPromptKind kind = _prompt.Kind; bool flag2 = (uint)(kind - 2) <= 1u; if (flag2 && Input.GetKeyDown((KeyCode)101)) { if (_prompt.Kind == SyndicateHqPromptKind.Enter) { TryEnter(); } else { TryExit(); } } } catch (Exception ex) { _log("Syndicate HQ update was isolated: " + ex.Message); } } public void OnGUI() { //IL_0075: 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_0082: 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_0092: Unknown result type (might be due to invalid IL or missing references) //IL_009c: Expected O, but got Unknown //IL_009d: Expected O, but got Unknown //IL_00a3: 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_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_00d7: Expected O, but got Unknown //IL_0130: Unknown result type (might be due to invalid IL or missing references) if (_disposed || _prompt.Kind == SyndicateHqPromptKind.None) { return; } try { float num = Mathf.Min(560f, Mathf.Max(320f, (float)Screen.width - 32f)); Rect val = default(Rect); ((Rect)(ref val))..ctor((float)Screen.width / 2f - num / 2f, (float)Screen.height - 156f - 28f, num, 156f); GUIStyle val2 = new GUIStyle(GUI.skin.box) { fontSize = 22, wordWrap = true, padding = new RectOffset(22, 22, 18, 18) }; val2.normal.textColor = Color.white; GUI.Box(val, _prompt.Text, val2); GUIStyle val3 = new GUIStyle(GUI.skin.button) { fontSize = 20 }; Rect val4 = default(Rect); ((Rect)(ref val4))..ctor(((Rect)(ref val)).x + 42f, ((Rect)(ref val)).yMax - 48f - 18f, ((Rect)(ref val)).width - 84f, 48f); SyndicateHqPromptKind kind = _prompt.Kind; bool flag = (uint)(kind - 2) <= 1u; if (flag && GUI.Button(val4, _prompt.ActionText + " [E]", val3)) { if (_prompt.Kind == SyndicateHqPromptKind.Enter) { TryEnter(); } else { TryExit(); } } } catch (Exception ex) { _log("Syndicate HQ presentation was isolated: " + ex.Message); } } public void Dispose() { if (_disposed) { return; } if (!TryTeardown()) { _log("Syndicate HQ disposal is blocked until a safe teardown succeeds."); return; } try { if (!_doorObserver.TryDispose()) { _teardownBlocked = true; _machine.Fault(); _log("Syndicate HQ listener disposal remained retryable."); } else { _interior.Dispose(); _disposed = true; _machine.Dispose(); } } catch (Exception ex) { _log("Syndicate HQ disposal failed and remains retryable: " + ex.Message); } } public void HandleDoorInteraction(SyndicateHqDoorFingerprint fingerprint, long generation) { bool flag = _disposed || _teardownBlocked || !_runtimePrepared || !_doorObserver.IsCurrent(fingerprint, generation); if (!flag) { SyndicateHqEntryState state = _machine.State; bool flag2 = (uint)(state - 4) <= 3u; flag = flag2; } if (flag) { return; } try { if (_context.TryRead(out var snapshot) == Release1StoryHostContextReadStatus.Ready) { SyndicateHqUnlockDecision syndicateHqUnlockDecision = _authority.Evaluate(); _machine.ObserveDoor(syndicateHqUnlockDecision.IsUnlocked); ShowPrompt(syndicateHqUnlockDecision.IsUnlocked ? new SyndicateHqPrompt(SyndicateHqPromptKind.Enter, "Syndicate HQ is ready.", "Enter Syndicate HQ") : new SyndicateHqPrompt(SyndicateHqPromptKind.Locked, "Syndicate HQ is locked.", string.Empty), syndicateHqUnlockDecision.IsUnlocked ? 5f : 3f, generation, fingerprint, snapshot); } } catch (Exception ex) { _log("Syndicate HQ door admission was isolated: " + ex.Message); } } public bool TryEnter() { try { if (_disposed || _teardownBlocked || !_runtimePrepared || _machine.State != SyndicateHqEntryState.EnterPrompt || !IsCurrentPrompt() || !_authority.Evaluate().IsUnlocked) { return false; } SyndicateHqDoorFingerprint currentFingerprint = _doorObserver.CurrentFingerprint; if ((object)currentFingerprint == null || _context.TryRead(out var snapshot) != Release1StoryHostContextReadStatus.Ready || !SameContext(snapshot, _promptContext.Value) || !_doorObserver.IsCurrent(_promptFingerprint, _promptGeneration)) { return false; } if (!_transport.TryCapture(currentFingerprint, snapshot, out SyndicateHqExteriorReturnState state) || !_interior.TryGetMarker("HQ_ENTRY", out var position)) { return false; } _returnState = state; _returnedForTeardown = false; if (!_machine.BeginEntry(returnStateValid: true)) { return false; } if (!_transport.TryTeleport(position)) { _machine.CompleteEntry(succeeded: false); return false; } _machine.CompleteEntry(succeeded: true); _exitPromptArmed = false; ClearPrompt(); return true; } catch (Exception ex) { _log("Syndicate HQ entry failed safely: " + ex.Message); _machine.Fault(); return false; } } public bool TryExit() { if (_disposed || _teardownBlocked || !_runtimePrepared || _machine.State != SyndicateHqEntryState.Inside) { return false; } try { if (_context.TryRead(out var snapshot) != Release1StoryHostContextReadStatus.Ready) { return false; } SyndicateHqReturnPlan syndicateHqReturnPlan = SyndicateHqReturnFallbackPlanner.Plan(_returnState, snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId); if (syndicateHqReturnPlan.Path == SyndicateHqReturnPath.NoSafeReturn || !_machine.BeginExit()) { return false; } if (!_transport.TryTeleport(syndicateHqReturnPlan.Position)) { _machine.CompleteExit(succeeded: false); return false; } _machine.CompleteExit(succeeded: true); _returnState = null; _returnedForTeardown = false; ClearPrompt(); return true; } catch (Exception ex) { _log("Syndicate HQ exit failed safely: " + ex.Message); _machine.Fault(); return false; } } public bool TryTeardown() { if (_disposed) { return true; } try { bool flag = (object)_returnState != null && !_returnedForTeardown; if (flag) { SyndicateHqEntryState state = _machine.State; bool flag2 = (uint)(state - 4) <= 3u; flag = flag2 || _teardownBlocked; } if (flag && !TryReturnForTeardown()) { _teardownBlocked = true; _recoveryRequiresFreshAccessPoint = true; _machine.Fault(); return false; } bool flag3 = true; try { flag3 &= _doorObserver.Detach(); } catch (Exception ex) { flag3 = false; _log("Syndicate HQ listener teardown remained retryable: " + ex.Message); } try { _interior.Destroy(); } catch (Exception ex2) { flag3 = false; _log("Syndicate HQ interior teardown remained retryable: " + ex2.Message); } if (!(flag3 & (_doorObserver.SubscriptionCount == 0 && !_interior.IsCreated))) { _teardownBlocked = true; _machine.Fault(); return false; } _returnState = null; _returnedForTeardown = false; _recoveryRequiresFreshAccessPoint = false; _runtimePrepared = false; _teardownBlocked = false; ClearPrompt(); _machine.Reset(); return true; } catch (Exception ex3) { _teardownBlocked = true; _machine.Fault(); _log("Syndicate HQ teardown remained retryable: " + ex3.Message); return false; } } private bool TryReturnForTeardown() { if (_context.TryRead(out var snapshot) != Release1StoryHostContextReadStatus.Ready) { return false; } SyndicateHqReturnPlan syndicateHqReturnPlan; if (_recoveryRequiresFreshAccessPoint) { if (!_doorObserver.TryResolveFresh(out SyndicateHqDoorFingerprint fingerprint)) { return false; } syndicateHqReturnPlan = SyndicateHqReturnFallbackPlanner.PlanFreshAccessPoint(_returnState, snapshot.SessionEpoch, snapshot.PlayerId, fingerprint.AccessPointPosition); } else { syndicateHqReturnPlan = SyndicateHqReturnFallbackPlanner.Plan(_returnState, snapshot.SessionEpoch, snapshot.LoadEpoch, snapshot.PlayerId); } if (syndicateHqReturnPlan.Path == SyndicateHqReturnPath.NoSafeReturn) { return false; } if (_machine.State == SyndicateHqEntryState.Inside && !_machine.BeginExit()) { return false; } if (_machine.State == SyndicateHqEntryState.Faulted && !_machine.BeginRecoveryExit()) { return false; } try { if (!_transport.TryTeleport(syndicateHqReturnPlan.Position)) { return false; } bool num = _machine.CompleteExit(succeeded: true); if (num) { _returnedForTeardown = true; } return num; } catch (Exception ex) { _log("Syndicate HQ safe return failed: " + ex.Message); return false; } } private bool TeardownAtBoundary() { bool num = TryTeardown(); if (!num) { _recoveryRequiresFreshAccessPoint = true; } return num; } private void FailActivationCleanup() { _runtimePrepared = false; bool flag = false; try { flag = _doorObserver.Detach(); } catch (Exception ex) { _log("Syndicate HQ activation cleanup remained retryable: " + ex.Message); } _teardownBlocked = !flag || _doorObserver.SubscriptionCount != 0; _machine.Fault(); } private bool IsCurrentPrompt() { if (_prompt.Kind == SyndicateHqPromptKind.Enter && (object)_promptFingerprint != null) { Release1StoryHostContextSnapshot? promptContext = _promptContext; if (promptContext.HasValue) { return _doorObserver.IsCurrent(_promptFingerprint, _promptGeneration); } } return false; } private void ShowPrompt(SyndicateHqPrompt prompt, float seconds, long generation, SyndicateHqDoorFingerprint? fingerprint, Release1StoryHostContextSnapshot context) { _prompt = prompt; _promptExpiresAt = _now() + seconds; _promptGeneration = generation; _promptFingerprint = fingerprint; _promptContext = context; } private void ClearPrompt() { _prompt = new SyndicateHqPrompt(SyndicateHqPromptKind.None, string.Empty, string.Empty); _promptFingerprint = null; _promptContext = null; _promptGeneration = 0L; } private static bool SameContext(Release1StoryHostContextSnapshot left, Release1StoryHostContextSnapshot right) { if (left.SessionEpoch == right.SessionEpoch && left.LoadEpoch == right.LoadEpoch && string.Equals(left.PlayerId, right.PlayerId, StringComparison.Ordinal)) { return string.Equals(left.ActiveSaveFolder, right.ActiveSaveFolder, StringComparison.OrdinalIgnoreCase); } return false; } } public sealed class SyndicateHqNativePlayerTransport : ISyndicateHqPlayerTransport { private readonly Action _log; private Player? _player; public SyndicateHqNativePlayerTransport(Action? log = null) { _log = log ?? ((Action)delegate { }); } public bool TryCapture(SyndicateHqDoorFingerprint door, Release1StoryHostContextSnapshot context, out SyndicateHqExteriorReturnState state) { //IL_003b: Unknown result type (might be due to invalid IL or missing references) state = null; _player = FindCanonicalPlayer(context.PlayerId); if (_player == null) { _log("Syndicate HQ transport could not resolve the canonical player."); return false; } state = new SyndicateHqExteriorReturnState(ToValue(((Component)_player).transform.position), door.AccessPointPosition, context.SessionEpoch, context.LoadEpoch, context.PlayerId); return true; } public bool TryTeleport(SyndicateHqVector3 position) { //IL_003d: Unknown result type (might be due to invalid IL or missing references) try { if (_player == null) { _log("Syndicate HQ transport had no captured canonical player."); return false; } if (!PlayerSingleton.InstanceExists) { _log("Syndicate HQ transport could not resolve PlayerSingleton."); return false; } PlayerSingleton.Instance.Teleport(ToUnity(position), true); Physics.SyncTransforms(); return true; } catch (Exception ex) { _log("Syndicate HQ native teleport failed: " + ex.Message); return false; } } public bool TryReadPosition(out SyndicateHqVector3 position) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) if (_player == null) { position = default(SyndicateHqVector3); return false; } position = ToValue(((Component)_player).transform.position); return true; } private static Player? FindCanonicalPlayer(string playerId) { try { List playerList = Player.PlayerList; if (playerList == null) { return null; } Player val = null; Enumerator enumerator = playerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if (current != null && ((NetworkBehaviour)current).IsServerInitialized && current.Connection != null && string.Equals(current.PlayerCode?.Trim(), playerId, StringComparison.Ordinal)) { if (val != null) { return null; } val = current; } } return val; } catch { return null; } } private static SyndicateHqVector3 ToValue(Vector3 value) { //IL_0000: 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) return new SyndicateHqVector3(value.x, value.y, value.z); } private static Vector3 ToUnity(SyndicateHqVector3 value) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(value.X, value.Y, value.Z); } } public sealed class SyndicateHqStorageLoadContextAdapter { public bool TryRead(string? nativeMainPath, out SyndicateHqStorageContext context, out string reason) { context = null; try { ServerManager serverManager = InstanceFinder.ServerManager; ClientManager clientManager = InstanceFinder.ClientManager; if (serverManager == null || clientManager == null || !serverManager.OneServerStarted() || !clientManager.Started) { reason = "The authoritative single-player host was not ready at the storage-load boundary."; return false; } if (!TryFindSaveRoot(nativeMainPath, out string saveRoot)) { LoadManager instance = Singleton.Instance; if (!TryFindSaveRoot((instance != null) ? instance.LoadedGameFolderPath : null, out saveRoot)) { reason = "The native storage-load path did not resolve to a canonical save root."; return false; } } if (!LocalPressureHostLifecyclePolicies.TryGetCanonicalHostIdentity(saveRoot, out string identity) || string.IsNullOrWhiteSpace(identity)) { reason = "The storage-load save root did not expose a canonical Steam identity."; return false; } List list = new List(); if (Player.PlayerList != null) { Enumerator enumerator = Player.PlayerList.GetEnumerator(); while (enumerator.MoveNext()) { Player current = enumerator.Current; if (current != null && ((NetworkBehaviour)current).IsServerInitialized && current.Connection != null) { list.Add(current); } } } if (list.Count > 1 || (list.Count == 1 && !string.Equals(list[0].PlayerCode?.Trim(), identity, StringComparison.Ordinal))) { reason = "The storage-load player registry was multiplayer or disagreed with the canonical save identity."; return false; } context = new SyndicateHqStorageContext(IsCanonicalHost: true, identity, saveRoot, saveRoot); reason = "Canonical host and save binding were resolved at the vanilla storage-load boundary."; return true; } catch (Exception ex) { context = null; reason = "Storage-load context resolution threw " + ex.GetType().Name + ": " + ex.Message; return false; } } public static bool TryFindSaveRoot(string? path, out string saveRoot) { saveRoot = string.Empty; if (string.IsNullOrWhiteSpace(path)) { return false; } try { for (DirectoryInfo directoryInfo = new DirectoryInfo(Path.GetFullPath(path)); directoryInfo != null; directoryInfo = directoryInfo.Parent) { if (directoryInfo.Name.StartsWith("SaveGame_", StringComparison.OrdinalIgnoreCase) && directoryInfo.Name.Length != "SaveGame_".Length && directoryInfo.Parent != null && directoryInfo.Parent.Parent != null && string.Equals(directoryInfo.Parent.Parent.Name, "Saves", StringComparison.OrdinalIgnoreCase) && LocalPressureHostLifecyclePolicies.TryGetCanonicalHostIdentity(directoryInfo.FullName, out string _)) { saveRoot = Path.TrimEndingDirectorySeparator(directoryInfo.FullName); return true; } } } catch { } return false; } } internal static class SyndicateHqStorageLoadPatch { private static ISyndicateHqStorageRuntime? _runtime; private static SyndicateHqStorageLoadContextAdapter? _context; private static Action _log = delegate { }; private static Action _receiptLog = delegate { }; private static bool _loadCycleAttempted; private static bool _loadCyclePrepared; public static bool Apply(Harmony harmony, ISyndicateHqStorageRuntime runtime, SyndicateHqStorageLoadContextAdapter context, Action? log = null, Action? receiptLog = null) { //IL_00e5: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Expected O, but got Unknown //IL_0173: Unknown result type (might be due to invalid IL or missing references) //IL_0181: Expected O, but got Unknown ArgumentNullException.ThrowIfNull(harmony, "harmony"); ArgumentNullException.ThrowIfNull(runtime, "runtime"); ArgumentNullException.ThrowIfNull(context, "context"); _runtime = runtime; _context = context; _log = log ?? ((Action)delegate { }); _receiptLog = receiptLog ?? _log; try { MethodInfo methodInfo = AccessTools.Method(typeof(PlaceableStorageEntityLoader), "Load", new Type[1] { typeof(DynamicSaveData) }, (Type[])null) ?? throw new MissingMethodException(typeof(StorageLoader).FullName, "Load"); MethodInfo methodInfo2 = AccessTools.Method(typeof(SyndicateHqStorageLoadPatch), "Prefix", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(SyndicateHqStorageLoadPatch).FullName, "Prefix"); harmony.Patch((MethodBase)methodInfo, new HarmonyMethod(methodInfo2), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); MethodInfo methodInfo3 = AccessTools.Method(typeof(BuildableItem), "SetCulled", new Type[1] { typeof(bool) }, (Type[])null) ?? throw new MissingMethodException(typeof(BuildableItem).FullName, "SetCulled"); MethodInfo methodInfo4 = AccessTools.Method(typeof(SyndicateHqStorageLoadPatch), "CullingPrefix", (Type[])null, (Type[])null) ?? throw new MissingMethodException(typeof(SyndicateHqStorageLoadPatch).FullName, "CullingPrefix"); harmony.Patch((MethodBase)methodInfo3, new HarmonyMethod(methodInfo4), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); return true; } catch (Exception ex) { _runtime = null; _context = null; _log("Syndicate HQ storage-load prefix was not applied: " + ex.GetType().Name + ": " + ex.Message); return false; } } public static void Reset() { _runtime?.DisableInteractions(); _runtime = null; _context = null; _loadCycleAttempted = false; _loadCyclePrepared = false; SyndicateHqStorageCullingGuard.Reset(); _log = delegate { }; _receiptLog = delegate { }; } public static void BeginLoadCycle() { _loadCycleAttempted = false; _loadCyclePrepared = false; _runtime?.DisableInteractions(); } public static bool CompleteAfterVanillaLoad() { PrepareOnce(null); ISyndicateHqStorageRuntime runtime = _runtime; if (runtime == null || !_loadCyclePrepared) { return false; } try { SyndicateHqStorageResult syndicateHqStorageResult = runtime.CompleteVanillaLoadBoundary(); if (!syndicateHqStorageResult.IsReady) { _log($"Syndicate HQ standard closets remained inert after vanilla load: {syndicateHqStorageResult.Status} — {syndicateHqStorageResult.Reason}"); } return syndicateHqStorageResult.IsReady; } catch (Exception ex) { runtime.DisableInteractions(); _log("Syndicate HQ closet reconciliation failed closed: " + ex.GetType().Name + ": " + ex.Message); return false; } } private static void Prefix(DynamicSaveData __0) { PrepareOnce(null); } private static void CullingPrefix(BuildableItem __instance, ref bool __0) { //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 (__0) { PlaceableStorageEntity val = (PlaceableStorageEntity)(object)((__instance is PlaceableStorageEntity) ? __instance : null); if (val != null && !((Object)(object)val == (Object)null) && Guid.TryParse(((object)((BuildableItem)val).GUID/*cast due to .constrained prefix*/).ToString(), out var result)) { __0 = SyndicateHqStorageCullingGuard.Filter(result, __0); } } } private static void PrepareOnce(string? nativeMainPath) { ISyndicateHqStorageRuntime runtime = _runtime; SyndicateHqStorageLoadContextAdapter context = _context; if (runtime == null || context == null || _loadCycleAttempted) { return; } _loadCycleAttempted = true; try { if (!context.TryRead(nativeMainPath, out SyndicateHqStorageContext context2, out string reason)) { runtime.DisableInteractions(); _log("Syndicate HQ storage remained inert at native load: " + reason); return; } SyndicateHqStorageResult syndicateHqStorageResult = runtime.BeginLoad(context2); if (syndicateHqStorageResult.Status != SyndicateHqStorageStatus.NotPrepared) { runtime.DisableInteractions(); _log($"Syndicate HQ storage load admission was {syndicateHqStorageResult.Status}: {syndicateHqStorageResult.Reason}"); return; } SyndicateHqStorageResult syndicateHqStorageResult2 = runtime.PrepareAtVanillaLoadBoundary(); _loadCyclePrepared = syndicateHqStorageResult2.Status == SyndicateHqStorageStatus.NotPrepared; if (!_loadCyclePrepared) { _log($"Syndicate HQ storage remained inert at native load: {syndicateHqStorageResult2.Status} — {syndicateHqStorageResult2.Reason}"); } } catch (Exception ex) { runtime.DisableInteractions(); _log("Syndicate HQ storage-load prefix failed closed: " + ex.GetType().Name + ": " + ex.Message); } } } public static class TimingReceiptThresholdPreference { private const string CategoryIdentifier = "OrganizedCrime"; private const string EntryIdentifier = "TimingReceiptThresholdMs"; public static int Read() { MelonPreferences_Category val = MelonPreferences.GetCategory("OrganizedCrime") ?? MelonPreferences.CreateCategory("OrganizedCrime"); return (val.GetEntry("TimingReceiptThresholdMs") ?? val.CreateEntry("TimingReceiptThresholdMs", 50, (string)null, (string)null, false, false, (ValueValidator)null, (string)null)).Value; } } public sealed class UnityNativeNavigationAdapter : IFishWarehouseNativeNavigationAdapter { private readonly Transform _propertyRoot; private readonly Dictionary _data = new Dictionary(); private readonly Dictionary _links = new Dictionary(); public UnityNativeNavigationAdapter(Transform propertyRoot) { _propertyRoot = propertyRoot ?? throw new ArgumentNullException("propertyRoot"); } public IReadOnlyList GetAgentSettings() { //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) List list = new List(); for (int i = 0; i < NavMesh.GetSettingsCount(); i++) { NavMeshBuildSettings settingsByIndex = NavMesh.GetSettingsByIndex(i); int agentTypeID = ((NavMeshBuildSettings)(ref settingsByIndex)).agentTypeID; list.Add(new FishWarehouseNativeNavigationAgentSetting(agentTypeID, NavMesh.GetSettingsNameFromID(agentTypeID) ?? string.Empty)); } return list; } public IReadOnlyList FindLiveEmployeeAgents(FishWarehouseNativeNavigationEmployeeScope scope) { List list = new List(); switch (scope) { case FishWarehouseNativeNavigationEmployeeScope.PropertyAssigned: { Property component = ((Component)_propertyRoot).GetComponent(); if (IsAlive((Object?)(object)component) && component.Employees != null) { Enumerator enumerator2 = component.Employees.GetEnumerator(); while (enumerator2.MoveNext()) { Employee current2 = enumerator2.Current; AddLiveEmployeeAgent(current2, list); } } return list; } default: throw new ArgumentOutOfRangeException("scope", scope, null); case FishWarehouseNativeNavigationEmployeeScope.Global: { foreach (Employee item in Resources.FindObjectsOfTypeAll()) { AddLiveEmployeeAgent(item, list); } return list; } } } public FishWarehouseNativeNavigationRuntimeGraphProbe ProbeGraph(int agentTypeId, FishWarehouseNativeNavigationWorldDescriptor world) { //IL_0016: 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_004b: 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_007b: Unknown result type (might be due to invalid IL or missing references) NavMeshQueryFilter val = CreateFilter(agentTypeId); NavMeshHit val2 = default(NavMeshHit); bool flag = NavMesh.SamplePosition(ToUnity(world.Link.NominalExteriorQueryPoint), ref val2, world.SampleRadius, val); NavMeshHit val3 = default(NavMeshHit); bool flag2 = NavMesh.SamplePosition(ToUnity(world.DoorwayInterior), ref val3, world.SampleRadius, val); bool pathComplete = flag && flag2 && CalculatePath(((NavMeshHit)(ref val2)).position, ((NavMeshHit)(ref val3)).position, val) == FishWarehouseNativeNavigationPathStatus.PathComplete; FishWarehouseNativeNavigationVector exteriorPosition = (flag ? ToModelVector(((NavMeshHit)(ref val2)).position) : world.Link.NominalExteriorQueryPoint); return new FishWarehouseNativeNavigationRuntimeGraphProbe(agentTypeId, flag, exteriorPosition, flag2, pathComplete); } public int AddSyntheticSurface(int agentTypeId, FishWarehouseNativeNavigationWorldDescriptor world) { //IL_0001: 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_001a: 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_0068: 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_0077: 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) NavMeshBuildSettings settingsByID = NavMesh.GetSettingsByID(agentTypeId); List val = CreateSources(world); NavMeshData obj = NavMeshBuilder.BuildNavMeshData(settingsByID, val, ToUnityBounds(world.BakeBounds), Vector3.zero, Quaternion.identity); if (!IsAlive((Object?)(object)obj)) { throw new InvalidOperationException($"Fish Warehouse native navigation produced no NavMeshData for agent ID {agentTypeId}."); } NavMeshDataInstance value = NavMesh.AddNavMeshData(obj, Vector3.zero, Quaternion.identity); if (!((NavMeshDataInstance)(ref value)).valid) { throw new InvalidOperationException($"Fish Warehouse native navigation added an invalid NavMeshData instance for agent ID {agentTypeId}."); } _data.Add(((NavMeshDataInstance)(ref value)).id, value); return ((NavMeshDataInstance)(ref value)).id; } public int AddLink(int agentTypeId, FishWarehouseNativeNavigationWorldDescriptor world, FishWarehouseNativeNavigationVector exteriorEndpoint) { //IL_0002: 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_0025: 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) //IL_0069: 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) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_00c6: Unknown result type (might be due to invalid IL or missing references) NavMeshLinkData val = default(NavMeshLinkData); ((NavMeshLinkData)(ref val)).startPosition = ToUnity(world.Link.Start); ((NavMeshLinkData)(ref val)).endPosition = ToUnity(exteriorEndpoint); ((NavMeshLinkData)(ref val)).agentTypeID = agentTypeId; ((NavMeshLinkData)(ref val)).area = 0; ((NavMeshLinkData)(ref val)).costModifier = -1f; ((NavMeshLinkData)(ref val)).width = world.Link.Width; ((NavMeshLinkData)(ref val)).bidirectional = true; NavMeshLinkInstance value = NavMesh.AddLink(val, Vector3.zero, Quaternion.identity); if (!((NavMeshLinkInstance)(ref value)).valid) { throw new InvalidOperationException($"Fish Warehouse native navigation added an invalid NavMeshLink instance for agent ID {agentTypeId}."); } _links.Add(((NavMeshLinkInstance)(ref value)).id, value); return ((NavMeshLinkInstance)(ref value)).id; } public void ClearNativeSampleCache() { NavMeshUtility.ClearCache(); } public FishWarehouseNativeNavigationPathObservation ValidatePath(FishWarehouseNativeNavigationGraphRole graphRole, int agentTypeId, string targetName, FishWarehouseNativeNavigationVector start, FishWarehouseNativeNavigationVector end) { //IL_0009: 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) //IL_003a: 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) NavMeshQueryFilter val = CreateFilter(agentTypeId); NavMeshHit val2 = default(NavMeshHit); bool num = NavMesh.SamplePosition(ToUnity(start), ref val2, 1f, val); NavMeshHit val3 = default(NavMeshHit); bool flag = NavMesh.SamplePosition(ToUnity(end), ref val3, 1f, val); FishWarehouseNativeNavigationPathStatus pathStatus = ((num && flag) ? CalculatePath(((NavMeshHit)(ref val2)).position, ((NavMeshHit)(ref val3)).position, val) : FishWarehouseNativeNavigationPathStatus.PathInvalid); return new FishWarehouseNativeNavigationPathObservation(num, flag, pathStatus); } public void Remove(FishWarehouseNativeNavigationTeardownOperation operation) { switch (operation.Kind) { case FishWarehouseNativeNavigationTeardownOperationKind.RemoveLink: RemoveLink(operation.ResourceKey); break; case FishWarehouseNativeNavigationTeardownOperationKind.RemoveData: RemoveData(operation.ResourceKey); break; case FishWarehouseNativeNavigationTeardownOperationKind.ClearNativeSampleCache: ClearNativeSampleCache(); break; default: throw new ArgumentOutOfRangeException("operation", operation.Kind, null); } } private List CreateSources(FishWarehouseNativeNavigationWorldDescriptor worldDescriptor) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_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_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_006a: 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) NavMeshBuildSource val = default(NavMeshBuildSource); ((NavMeshBuildSource)(ref val)).shape = (NavMeshBuildSourceShape)2; ((NavMeshBuildSource)(ref val)).size = ToUnity(worldDescriptor.Surface.Size); ((NavMeshBuildSource)(ref val)).transform = Matrix4x4.TRS(ToUnity(worldDescriptor.Surface.Center), _propertyRoot.rotation, Vector3.one); ((NavMeshBuildSource)(ref val)).area = 0; ((NavMeshBuildSource)(ref val)).generateLinks = false; NavMeshBuildSource val2 = val; List obj = new List(); obj.Add(val2); return obj; } private static NavMeshQueryFilter CreateFilter(int agentTypeId) { //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_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0014: Expected O, but got Unknown return new NavMeshQueryFilter { agentTypeID = agentTypeId, areaMask = -1 }; } private static FishWarehouseNativeNavigationPathStatus CalculatePath(Vector3 start, Vector3 end, NavMeshQueryFilter filter) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //IL_0006: 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) //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_001a: 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) //IL_001f: Invalid comparison between Unknown and I4 NavMeshPath val = new NavMeshPath(); if (!NavMesh.CalculatePath(start, end, filter, val)) { return FishWarehouseNativeNavigationPathStatus.PathInvalid; } NavMeshPathStatus status = val.status; if ((int)status != 0) { if ((int)status == 1) { return FishWarehouseNativeNavigationPathStatus.PathPartial; } return FishWarehouseNativeNavigationPathStatus.PathInvalid; } return FishWarehouseNativeNavigationPathStatus.PathComplete; } private void AddLiveEmployeeAgent(Employee employee, ICollection agents) { if (IsAlive((Object?)(object)employee) && IsAlive((Object?)(object)((NPC)employee).Movement) && IsAlive((Object?)(object)((NPC)employee).Movement.Agent)) { int agentTypeID = ((NPC)employee).Movement.Agent.agentTypeID; string identity = $"{((Object)employee).name}#{((Object)employee).GetInstanceID()}"; agents.Add(new FishWarehouseNativeNavigationLiveEmployeeAgent(identity, agentTypeID)); } } private static Bounds ToUnityBounds(FishWarehouseNativeNavigationBox value) { //IL_0007: 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_0018: Unknown result type (might be due to invalid IL or missing references) return new Bounds(ToUnity(value.Center), ToUnity(value.Size)); } private static FishWarehouseNativeNavigationVector ToModelVector(Vector3 value) { //IL_0000: 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) return new FishWarehouseNativeNavigationVector(value.x, value.y, value.z); } private static Vector3 ToUnity(FishWarehouseNativeNavigationVector value) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) return new Vector3(value.X, value.Y, value.Z); } private static bool IsAlive(Object? value) { try { return value != null && value != (Object)null; } catch { return false; } } private void RemoveLink(int? resourceKey) { if (!resourceKey.HasValue) { return; } int valueOrDefault = resourceKey.GetValueOrDefault(); if (_links.TryGetValue(valueOrDefault, out var value)) { if (((NavMeshLinkInstance)(ref value)).valid) { ((NavMeshLinkInstance)(ref value)).Remove(); } _links.Remove(valueOrDefault); } } private void RemoveData(int? resourceKey) { if (!resourceKey.HasValue) { return; } int valueOrDefault = resourceKey.GetValueOrDefault(); if (_data.TryGetValue(valueOrDefault, out var value)) { if (((NavMeshDataInstance)(ref value)).valid) { ((NavMeshDataInstance)(ref value)).Remove(); } _data.Remove(valueOrDefault); } } } } namespace OrganizedCrime.Persistence { public sealed record FishWarehouseNativePropertyObjectSnapshot(string Guid, string GridGuid, int LoadOrder, string ItemId, bool IsSavedHome, string RawJson); public sealed record FishWarehouseNativePropertyEmployeeSnapshot(string Guid, string DataType, string Identity, bool PaidForToday, string BedGuid, IReadOnlyList StationGuids, string RawJson); public sealed record FishWarehouseNativePropertySnapshot(string PropertyCode, bool IsOwned, IReadOnlyList Objects, IReadOnlyList Employees); public sealed class FishWarehouseNativePropertySnapshotInspector { public bool TryInspect(byte[] nativePropertyBytes, out FishWarehouseNativePropertySnapshot snapshot, out string? failureReason) { snapshot = null; failureReason = null; if (nativePropertyBytes == null || nativePropertyBytes.Length == 0) { failureReason = "Fish Warehouse native property bytes were unavailable for inspection."; return false; } try { using JsonDocument jsonDocument = JsonDocument.Parse(nativePropertyBytes); JsonElement rootElement = jsonDocument.RootElement; FishWarehouseNativePropertyObjectSnapshot[] array = ReadObjects(rootElement.GetProperty("Objects")); JsonElement property = rootElement.GetProperty("Employees"); FishWarehouseNativePropertyEmployeeSnapshot[] array2 = ReadEmployees(property); snapshot = new FishWarehouseNativePropertySnapshot(ReadPropertyCode(rootElement, property), rootElement.GetProperty("IsOwned").GetBoolean(), Array.AsReadOnly(array), Array.AsReadOnly(array2)); return true; } catch (Exception ex) when (((ex is JsonException || ex is InvalidOperationException || ex is FormatException || ex is KeyNotFoundException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse native property inspection failed: " + ex.Message; return false; } } private static FishWarehouseNativePropertyObjectSnapshot[] ReadObjects(JsonElement objects) { if (objects.ValueKind != JsonValueKind.Array) { throw new JsonException("Native Fish Warehouse Objects must be an array."); } return objects.EnumerateArray().Select(delegate(JsonElement record) { JsonElement value; JsonElement jsonElement = (record.TryGetProperty("BaseData", out value) ? ReadNestedObject(record, "BaseData") : record); string text = ReadRequiredString(ReadNestedObject(jsonElement, "ItemString"), "ID"); return new FishWarehouseNativePropertyObjectSnapshot(ReadRequiredString(jsonElement, "GUID"), ReadRequiredString(jsonElement, "GridGUID"), jsonElement.GetProperty("LoadOrder").GetInt32(), text, string.Equals(text, "locker", StringComparison.Ordinal), record.GetRawText()); }).ToArray(); } private static FishWarehouseNativePropertyEmployeeSnapshot[] ReadEmployees(JsonElement employees) { if (employees.ValueKind != JsonValueKind.Array) { throw new JsonException("Native Fish Warehouse Employees must be an array."); } return employees.EnumerateArray().Select(delegate(JsonElement record) { JsonElement jsonElement = ReadNestedObject(record, "BaseData"); string dataType = ReadRequiredString(record, "DataType"); return new FishWarehouseNativePropertyEmployeeSnapshot(ReadRequiredString(jsonElement, "GUID"), dataType, ReadEmployeeIdentity(jsonElement, dataType), jsonElement.GetProperty("PaidForToday").GetBoolean(), ReadEmployeeBedGuid(jsonElement, record), ReadConfigurationStationGuids(record), record.GetRawText()); }).ToArray(); } private static string ReadPropertyCode(JsonElement root, JsonElement employeeRecords) { if (root.TryGetProperty("PropertyCode", out var _)) { return ReadRequiredString(root, "PropertyCode"); } if (employeeRecords.ValueKind == JsonValueKind.Array) { foreach (JsonElement item in employeeRecords.EnumerateArray()) { if (ReadNestedObject(item, "BaseData").TryGetProperty("AssignedProperty", out var value2)) { string text = value2.GetString(); if (!string.IsNullOrWhiteSpace(text)) { return text; } } } } return ReadRequiredString(root, "PropertyCode"); } private static string ReadEmployeeIdentity(JsonElement baseData, string dataType) { if (baseData.TryGetProperty("Identity", out var _)) { return ReadRequiredString(baseData, "Identity"); } if (string.Equals(dataType, "PackagerData", StringComparison.Ordinal)) { return "Packager"; } return ReadRequiredString(baseData, "Identity"); } private static string ReadEmployeeBedGuid(JsonElement baseData, JsonElement employeeRecord) { if (baseData.TryGetProperty("BedGUID", out var _)) { return ReadRequiredString(baseData, "BedGUID"); } JsonElement? jsonElement = ReadConfigurationContents(employeeRecord); if (jsonElement.HasValue) { JsonElement valueOrDefault = jsonElement.GetValueOrDefault(); if (valueOrDefault.ValueKind == JsonValueKind.Object && valueOrDefault.TryGetProperty("Bed", out var value2)) { return ReadOptionalString(value2, "ObjectGUID"); } } throw new JsonException("Native Fish Warehouse Bed/ObjectGUID was unavailable."); } private static IReadOnlyList ReadConfigurationStationGuids(JsonElement employeeRecord) { JsonElement? jsonElement = ReadConfigurationContents(employeeRecord); if (jsonElement.HasValue) { JsonElement valueOrDefault = jsonElement.GetValueOrDefault(); return ReadStationGuids(valueOrDefault); } return Array.AsReadOnly(Array.Empty()); } private static IReadOnlyList ReadStationGuids(JsonElement contents) { if (contents.ValueKind == JsonValueKind.String) { using (JsonDocument jsonDocument = JsonDocument.Parse(contents.GetString() ?? string.Empty)) { return ReadStationGuids(jsonDocument.RootElement); } } if (contents.ValueKind != JsonValueKind.Object) { return Array.AsReadOnly(Array.Empty()); } if (contents.TryGetProperty("Stations", out var value) && value.ValueKind == JsonValueKind.Object && value.TryGetProperty("ObjectGUIDs", out var value2) && value2.ValueKind == JsonValueKind.Array) { return ReadStringArray(value2); } if (!contents.TryGetProperty("StationGUIDs", out var value3) || value3.ValueKind != JsonValueKind.Array) { return Array.AsReadOnly(Array.Empty()); } return ReadStringArray(value3); } private static IReadOnlyList ReadStringArray(JsonElement values) { return Array.AsReadOnly((from station in values.EnumerateArray() select station.GetString() into station where !string.IsNullOrWhiteSpace(station) select (station)).ToArray()); } private static JsonElement? ReadConfigurationContents(JsonElement employeeRecord) { if (!employeeRecord.TryGetProperty("AdditionalDatas", out var value) || value.ValueKind != JsonValueKind.Array) { return null; } foreach (JsonElement item in value.EnumerateArray()) { if (!item.TryGetProperty("Name", out var value2) || !string.Equals(value2.GetString(), "Configuration", StringComparison.Ordinal) || !item.TryGetProperty("Contents", out var value3)) { continue; } if (value3.ValueKind == JsonValueKind.String) { using (JsonDocument jsonDocument = JsonDocument.Parse(value3.GetString() ?? string.Empty)) { return jsonDocument.RootElement.Clone(); } } return value3; } return null; } private static JsonElement ReadNestedObject(JsonElement record, string propertyName) { JsonElement property = record.GetProperty(propertyName); if (property.ValueKind == JsonValueKind.String) { using (JsonDocument jsonDocument = JsonDocument.Parse(property.GetString() ?? string.Empty)) { if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object) { throw new JsonException("Native Fish Warehouse " + propertyName + " must be a JSON object."); } return jsonDocument.RootElement.Clone(); } } if (property.ValueKind != JsonValueKind.Object) { throw new JsonException("Native Fish Warehouse " + propertyName + " must be a JSON object."); } return property; } private static string ReadRequiredString(JsonElement element, string propertyName) { string? text = element.GetProperty(propertyName).GetString(); if (string.IsNullOrWhiteSpace(text)) { throw new JsonException("Native Fish Warehouse " + propertyName + " was unavailable."); } return text; } private static string ReadOptionalString(JsonElement element, string propertyName) { if (!element.TryGetProperty(propertyName, out var value) || value.ValueKind != JsonValueKind.String) { throw new JsonException("Native Fish Warehouse " + propertyName + " was unavailable."); } string text = value.GetString(); if (text == null || (text.Length > 0 && string.IsNullOrWhiteSpace(text))) { throw new JsonException("Native Fish Warehouse " + propertyName + " was unavailable."); } return text; } } public sealed class FishWarehousePersistenceService { private readonly IFishWarehouseSaveStore _store; private readonly Func _activeSaveFolder; private readonly Func _captureState; private readonly Action _applyState; private readonly Action _log; private readonly IFishWarehousePropertySnapshotStore _snapshotStore; private readonly FishWarehouseReplayLifecycle _replayLifecycle; private FishWarehouseSaveState? _currentState; public FishWarehouseSaveState? CurrentState => _currentState; public bool RequiresSnapshotProtection { get { if (!_replayLifecycle.RequiresSnapshotProtection) { return RequiresSnapshotProtectionFor(_currentState?.Replay); } return true; } } public FishWarehousePersistenceService(IFishWarehouseSaveStore store, Func activeSaveFolder, Func captureState, Action applyState, Action? log = null, IFishWarehousePropertySnapshotStore? snapshotStore = null, FishWarehouseReplayLifecycle? replayLifecycle = null) { _store = store; _activeSaveFolder = activeSaveFolder; _captureState = captureState; _applyState = applyState; _log = log ?? ((Action)delegate { }); _snapshotStore = snapshotStore ?? new FishWarehousePropertySnapshotStore(); _replayLifecycle = replayLifecycle ?? new FishWarehouseReplayLifecycle(); } public bool PrimeStateForLoad() { string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { _currentState = null; _log("Fish Warehouse pre-load state prime skipped because the active save folder was unavailable."); return false; } _currentState = null; if (!_store.TryRead(text, out FishWarehouseSaveState state, out string failureReason)) { _log("Fish Warehouse pre-load state was not primed: " + failureReason); return false; } if ((object)state.Replay != null && !_replayLifecycle.Begin(state.Replay, out failureReason)) { _log("Fish Warehouse pre-load replay state was not primed: " + failureReason); return false; } _currentState = ReconcileOwnership(state); return true; } public bool HandleLoadComplete() { string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { _log("Fish Warehouse save load skipped because the active save folder was unavailable."); return false; } if (_store.TryRead(text, out FishWarehouseSaveState state, out string failureReason)) { if ((object)state.Replay != null && !TryBeginLoadedReplay(text, state.Replay, out failureReason)) { _log("Fish Warehouse save state was not loaded: " + failureReason); return false; } state = (_currentState = ReconcileOwnership(state)); _applyState(state); _log("Fish Warehouse save state loaded; owned-state restoration is queued after runtime readiness."); return true; } _log("Fish Warehouse save state was not loaded: " + failureReason); return false; } public void HandleSaveComplete() { string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { _log("Fish Warehouse save skipped because the active save folder was unavailable."); return; } if (RequiresSnapshotProtection) { if (TryRestoreProtectedNativeProperty(text, out string failureReason)) { _log("WARNING: Fish Warehouse native property snapshot was restored to preserve the active replay; fresh runtime capture was skipped."); } else { _log("WARNING: Fish Warehouse native property snapshot preservation failed: " + failureReason); } return; } FishWarehouseSaveState fishWarehouseSaveState = _captureState(); FishWarehouseSaveState state = fishWarehouseSaveState with { Replay = (_currentState?.Replay ?? fishWarehouseSaveState.Replay), UnsupportedEmployees = (_currentState?.UnsupportedEmployees ?? fishWarehouseSaveState.UnsupportedEmployees) }; state = ReconcileOwnership(state); if (_store.TryWrite(text, state, out string failureReason2)) { _currentState = state; _log("Fish Warehouse save state written to the Organized Crime sidecar."); } else { _log("Fish Warehouse save state was not written: " + failureReason2); } } public bool TryPersistReplayCheckpoint(FishWarehouseReplayCheckpoint checkpoint) { string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { _log("Fish Warehouse replay checkpoint persistence skipped because the active save folder was unavailable."); return false; } if (!TryPreserveExistingState(text)) { _log("Fish Warehouse replay checkpoint was not persisted because the existing sidecar could not be validated without overwriting it."); return false; } if (!TrySynchronizeReplayCheckpoint(text, checkpoint, out string failureReason)) { _log("Fish Warehouse replay checkpoint was not persisted: " + failureReason); return false; } return TryPersistState(text, MergeCurrentState()with { Replay = checkpoint }); } public bool TryPersistRecoveredCaptureCheckpoint(FishWarehouseReplayCheckpoint checkpoint) { string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { _log("Fish Warehouse recovered capture checkpoint persistence skipped because the active save folder was unavailable."); return false; } string failureReason = null; if ((object)checkpoint == null || checkpoint.Phase != FishWarehouseReplayPhase.Captured || !HasActiveSaveFolderKey(text, checkpoint, out failureReason)) { _log("Fish Warehouse recovered capture checkpoint was not persisted: " + (failureReason ?? "checkpoint was invalid.")); return false; } if (!TryPreserveExistingState(text)) { _log("Fish Warehouse recovered capture checkpoint was not persisted because the existing sidecar could not be validated without overwriting it."); return false; } FishWarehouseReplayCheckpoint currentCheckpoint = _replayLifecycle.CurrentCheckpoint; if ((object)currentCheckpoint == null || currentCheckpoint.Phase != FishWarehouseReplayPhase.Failed || !string.Equals(currentCheckpoint.GenerationId, checkpoint.GenerationId, StringComparison.Ordinal) || !string.Equals(currentCheckpoint.SaveFolderKey, checkpoint.SaveFolderKey, StringComparison.Ordinal)) { _log("Fish Warehouse recovered capture checkpoint was not persisted because the active lifecycle was not the same capture failure."); return false; } return TryPersistState(text, MergeCurrentState()with { Replay = checkpoint }); } public bool TryPersistEmployeeAgentTypeId(int agentTypeId) { return TryPersistState(MergeCurrentState()with { EmployeeAgentTypeId = agentTypeId }); } public bool TryMergeUnsupportedEmployees(IReadOnlyList records) { if (records == null) { return false; } Dictionary dictionary = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (FishWarehouseUnsupportedEmployeeRecord unsupportedEmployee in MergeCurrentState().UnsupportedEmployees) { dictionary[unsupportedEmployee.Guid] = unsupportedEmployee; } foreach (FishWarehouseUnsupportedEmployeeRecord record in records) { dictionary[record.Guid] = record; } return TryPersistState(MergeCurrentState()with { UnsupportedEmployees = dictionary.Values.ToArray() }); } private FishWarehouseSaveState MergeCurrentState() { return _currentState ?? _captureState(); } private FishWarehouseSaveState ReconcileOwnership(FishWarehouseSaveState state) { if (!FishWarehouseOwnershipReconciler.IsOwnedByEvidence(state.Owned, state.Replay?.CapturedObjectCount ?? 0, state.Replay?.CapturedEmployeeCount ?? 0, null) || (state.Owned && state.Unlocked)) { return state; } if (!state.Owned) { _log("WARNING: Fish Warehouse ownership reconciled to owned=true from a corrupt sidecar " + $"owned=false because captured content (objects={state.Replay?.CapturedObjectCount ?? 0}, " + $"employees={state.Replay?.CapturedEmployeeCount ?? 0}) proves prior ownership " + "(ownership is monotonic — there is no sell path)."); } return state with { Owned = true, Unlocked = true }; } private bool TryPreserveExistingState(string saveFolder) { if ((object)_currentState != null) { return true; } if (_store.TryRead(saveFolder, out FishWarehouseSaveState state, out string _)) { if ((object)state != null) { _currentState = state; } return true; } if (!FishWarehouseSavePath.TryResolveSidecarPath(saveFolder, out string sidecarPath, out string failureReason2)) { _log("Fish Warehouse existing sidecar path could not be validated: " + failureReason2); return false; } if (File.Exists(sidecarPath)) { _log("Fish Warehouse existing sidecar could not be read; preserving it instead of overwriting it."); return false; } return true; } private bool TryPersistState(FishWarehouseSaveState state) { string text = _activeSaveFolder(); if (string.IsNullOrWhiteSpace(text)) { _log("Fish Warehouse persistence update skipped because the active save folder was unavailable."); return false; } return TryPersistState(text, state); } private bool TryPersistState(string saveFolder, FishWarehouseSaveState state) { state = ReconcileOwnership(state); if (!_store.TryWrite(saveFolder, state, out string failureReason)) { _log("Fish Warehouse persistence update was not written: " + failureReason); return false; } _currentState = state; return true; } private bool TryBeginLoadedReplay(string saveFolder, FishWarehouseReplayCheckpoint checkpoint, out string? failureReason) { if (!HasActiveSaveFolderKey(saveFolder, checkpoint, out failureReason)) { return false; } return _replayLifecycle.Begin(checkpoint, out failureReason); } private bool TrySynchronizeReplayCheckpoint(string saveFolder, FishWarehouseReplayCheckpoint checkpoint, out string? failureReason) { if (!HasActiveSaveFolderKey(saveFolder, checkpoint, out failureReason)) { return false; } FishWarehouseReplayCheckpoint currentCheckpoint = _replayLifecycle.CurrentCheckpoint; if (currentCheckpoint == checkpoint) { failureReason = null; return true; } if ((object)currentCheckpoint == null || currentCheckpoint.Phase == FishWarehouseReplayPhase.Complete) { if (checkpoint.Phase != FishWarehouseReplayPhase.Captured) { failureReason = "Fish Warehouse replay must begin at Captured."; return false; } return _replayLifecycle.Begin(checkpoint, out failureReason); } failureReason = "Fish Warehouse replay checkpoint was not the lifecycle's active phase."; return false; } private static bool HasActiveSaveFolderKey(string saveFolder, FishWarehouseReplayCheckpoint checkpoint, out string? failureReason) { try { if (string.Equals(FishWarehouseSaveFolderKey.Create(saveFolder), checkpoint.SaveFolderKey, StringComparison.Ordinal)) { failureReason = null; return true; } failureReason = "Fish Warehouse replay checkpoint belongs to a different active save folder."; return false; } catch (Exception ex) when (((ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse active save folder key could not be validated: " + ex.Message; return false; } } private bool TryRestoreProtectedNativeProperty(string saveFolder, out string? failureReason) { FishWarehouseReplayCheckpoint fishWarehouseReplayCheckpoint = _replayLifecycle.CurrentCheckpoint ?? _currentState?.Replay; if ((object)fishWarehouseReplayCheckpoint == null) { failureReason = "Fish Warehouse replay checkpoint was unavailable for snapshot preservation."; return false; } if (!FishWarehouseSavePath.TryResolveSnapshotPath(saveFolder, out string snapshotPath, out failureReason)) { return false; } if (!File.Exists(snapshotPath)) { failureReason = "Fish Warehouse protected snapshot file was unavailable for preservation."; return false; } FishWarehouseProtectedSnapshot snapshot = new FishWarehouseProtectedSnapshot(fishWarehouseReplayCheckpoint.GenerationId, fishWarehouseReplayCheckpoint.SaveFolderKey, fishWarehouseReplayCheckpoint.SnapshotRelativePath, fishWarehouseReplayCheckpoint.SnapshotSha256, checked((int)new FileInfo(snapshotPath).Length)); return _snapshotStore.TryRestoreNativeProperty(saveFolder, snapshot, out failureReason); } private static bool RequiresSnapshotProtectionFor(FishWarehouseReplayCheckpoint? replay) { if ((object)replay != null) { FishWarehouseReplayPhase phase = replay.Phase; if (phase >= FishWarehouseReplayPhase.Captured && phase <= FishWarehouseReplayPhase.DeliveriesReleased) { return true; } } if ((object)replay == null) { return false; } return replay.Phase == FishWarehouseReplayPhase.Failed; } } public sealed class FishWarehousePropertySnapshotStore : IFishWarehousePropertySnapshotStore { private const string SnapshotRelativePath = "OrganizedCrime/fish-warehouse.snapshot.json"; public bool TryProtect(string saveFolder, string generationId, byte[] bytes, out FishWarehouseProtectedSnapshot snapshot, out string? failureReason) { snapshot = null; failureReason = null; if (string.IsNullOrWhiteSpace(generationId)) { failureReason = "Fish Warehouse snapshot generation was unavailable."; return false; } if (bytes == null) { failureReason = "Fish Warehouse snapshot bytes were unavailable."; return false; } if (!FishWarehouseSavePath.TryResolveSnapshotPath(saveFolder, out string snapshotPath, out failureReason)) { return false; } try { WriteAtomically(snapshotPath, bytes); snapshot = new FishWarehouseProtectedSnapshot(generationId, FishWarehouseSaveFolderKey.Create(saveFolder), "OrganizedCrime/fish-warehouse.snapshot.json", Convert.ToHexString(SHA256.HashData(bytes)), bytes.Length); return true; } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is ArgumentException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse snapshot could not be protected: " + ex.Message; return false; } } public bool TryRead(string saveFolder, FishWarehouseProtectedSnapshot snapshot, out byte[] bytes, out string? failureReason) { bytes = Array.Empty(); if (!TryValidateSnapshot(saveFolder, snapshot, out string snapshotPath, out failureReason)) { return false; } try { bytes = File.ReadAllBytes(snapshotPath); if (bytes.Length != snapshot.ByteCount) { failureReason = "Fish Warehouse protected snapshot byte count did not match its descriptor."; bytes = Array.Empty(); return false; } if (!string.Equals(Convert.ToHexString(SHA256.HashData(bytes)), snapshot.Sha256, StringComparison.Ordinal)) { failureReason = "Fish Warehouse protected snapshot SHA-256 hash did not match its descriptor."; bytes = Array.Empty(); return false; } failureReason = null; return true; } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse protected snapshot could not be read: " + ex.Message; return false; } } public bool TryRestoreNativeProperty(string saveFolder, FishWarehouseProtectedSnapshot snapshot, out string? failureReason) { if (!TryRead(saveFolder, snapshot, out byte[] bytes, out failureReason)) { return false; } if (!TryResolveNativePropertyPath(saveFolder, out string nativePropertyPath, out failureReason)) { return false; } try { WriteAtomically(nativePropertyPath, bytes); return true; } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is ArgumentException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse native property snapshot could not be restored: " + ex.Message; return false; } } private static bool TryValidateSnapshot(string saveFolder, FishWarehouseProtectedSnapshot? snapshot, out string snapshotPath, out string? failureReason) { snapshotPath = string.Empty; failureReason = null; if ((object)snapshot == null || string.IsNullOrWhiteSpace(snapshot.GenerationId) || !string.Equals(snapshot.RelativePath, "OrganizedCrime/fish-warehouse.snapshot.json", StringComparison.Ordinal) || snapshot.ByteCount < 0 || !IsSha256(snapshot.Sha256)) { failureReason = "Fish Warehouse protected snapshot descriptor was invalid."; return false; } try { if (!string.Equals(FishWarehouseSaveFolderKey.Create(saveFolder), snapshot.SaveFolderKey, StringComparison.Ordinal)) { failureReason = "Fish Warehouse protected snapshot belongs to a different save folder."; return false; } } catch (Exception ex) when (((ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse save folder key could not be validated: " + ex.Message; return false; } return FishWarehouseSavePath.TryResolveSnapshotPath(saveFolder, out snapshotPath, out failureReason); } private static bool TryResolveNativePropertyPath(string saveFolder, out string nativePropertyPath, out string? failureReason) { nativePropertyPath = string.Empty; failureReason = null; if (!FishWarehouseSavePath.TryResolveSnapshotPath(saveFolder, out string _, out failureReason)) { return false; } return FishWarehouseSavePath.TryResolveNativePropertyPath(saveFolder, RuntimePropertyDefinition.FishWarehouse, out nativePropertyPath, out failureReason); } private static void WriteAtomically(string destinationPath, byte[] bytes) { string directoryName = Path.GetDirectoryName(destinationPath); if (string.IsNullOrWhiteSpace(directoryName)) { throw new IOException("The destination directory could not be resolved."); } string text = Path.Combine(directoryName, $".{Path.GetFileName(destinationPath)}.{Guid.NewGuid():N}.tmp"); try { Directory.CreateDirectory(directoryName); File.WriteAllBytes(text, bytes); File.Move(text, destinationPath, overwrite: true); } finally { if (File.Exists(text)) { File.Delete(text); } } } private static bool IsSha256(string value) { if (value.Length != 64) { return false; } for (int i = 0; i < value.Length; i++) { if (!Uri.IsHexDigit(value[i])) { return false; } } return true; } } public enum FishWarehouseReplayPhase { Inactive, Captured, RuntimeReady, ObjectsReplayed, NavigationReady, EmployeesReplayed, DeliveriesReleased, Complete, Failed } public sealed record FishWarehouseReplayCheckpoint(string GenerationId, string SaveFolderKey, FishWarehouseReplayPhase Phase, string SnapshotRelativePath, string SnapshotSha256, int CapturedObjectCount, int CapturedEmployeeCount, int ReplayedObjectCount = 0, int ReplayedEmployeeCount = 0, string? FailurePhase = null, string? FailureReason = null, IReadOnlyList? InventoryRestoredEmployeeGuids = null); public sealed record FishWarehouseUnsupportedEmployeeRecord(string Guid, string DataType, string Identity, string RawJson); public sealed record FishWarehouseCaptureRecoveryPrerequisites(bool ByteProtectionSucceeded, bool InspectionSucceeded, bool TypedPayloadRetentionSucceeded, bool CheckpointPersistenceSucceeded) { public bool AllSucceeded { get { if (ByteProtectionSucceeded && InspectionSucceeded && TypedPayloadRetentionSucceeded) { return CheckpointPersistenceSucceeded; } return false; } } } public sealed class FishWarehouseReplayLifecycle { private readonly Func _captureRecoveryPrerequisites; public FishWarehouseReplayCheckpoint? CurrentCheckpoint { get; private set; } public string? LastRecoveredFailureReason { get; private set; } public bool RequiresSnapshotProtection { get { FishWarehouseReplayCheckpoint currentCheckpoint = CurrentCheckpoint; if ((object)currentCheckpoint != null) { FishWarehouseReplayPhase phase = currentCheckpoint.Phase; if (phase >= FishWarehouseReplayPhase.Captured && phase <= FishWarehouseReplayPhase.DeliveriesReleased) { return true; } } FishWarehouseReplayCheckpoint? currentCheckpoint2 = CurrentCheckpoint; if ((object)currentCheckpoint2 == null) { return false; } return currentCheckpoint2.Phase == FishWarehouseReplayPhase.Failed; } } public FishWarehouseReplayLifecycle(Func? captureRecoveryPrerequisites = null) { _captureRecoveryPrerequisites = captureRecoveryPrerequisites ?? ((Func)((FishWarehouseReplayCheckpoint _) => new FishWarehouseCaptureRecoveryPrerequisites(ByteProtectionSucceeded: false, InspectionSucceeded: false, TypedPayloadRetentionSucceeded: false, CheckpointPersistenceSucceeded: false))); } public bool Begin(FishWarehouseReplayCheckpoint checkpoint, out string? failureReason) { failureReason = null; if (!IsReplayCheckpoint(checkpoint)) { failureReason = "Fish Warehouse replay checkpoint was invalid."; return false; } if ((object)CurrentCheckpoint == null || CurrentCheckpoint.Phase == FishWarehouseReplayPhase.Complete) { CurrentCheckpoint = checkpoint; return true; } if (!string.Equals(CurrentCheckpoint.SaveFolderKey, checkpoint.SaveFolderKey, StringComparison.Ordinal)) { failureReason = "Fish Warehouse replay checkpoint belongs to a different save folder."; return false; } if (!string.Equals(CurrentCheckpoint.GenerationId, checkpoint.GenerationId, StringComparison.Ordinal)) { failureReason = "Fish Warehouse replay generation is already active."; return false; } if (CurrentCheckpoint == checkpoint) { return true; } failureReason = "Fish Warehouse replay generation is already active."; return false; } public bool TryAdvance(FishWarehouseReplayPhase phase, out string? failureReason) { failureReason = null; if ((object)CurrentCheckpoint == null) { failureReason = "Fish Warehouse replay has not started."; return false; } if (CurrentCheckpoint.Phase == FishWarehouseReplayPhase.Failed) { failureReason = "Fish Warehouse replay has failed and is terminal."; return false; } if (CurrentCheckpoint.Phase == phase) { return true; } FishWarehouseReplayPhase? nextPhase = GetNextPhase(CurrentCheckpoint.Phase); if (nextPhase != phase) { failureReason = ((phase == FishWarehouseReplayPhase.Captured) ? $"Fish Warehouse replay is already at {CurrentCheckpoint.Phase}." : $"Fish Warehouse replay cannot advance to {phase}; expected {nextPhase?.ToString() ?? "no later phase"}."); return false; } CurrentCheckpoint = CurrentCheckpoint with { Phase = phase, FailurePhase = null, FailureReason = null }; return true; } public bool RecordInventoryRestoredEmployeeGuids(IReadOnlyCollection employeeGuids, out string? failureReason) { failureReason = null; if ((object)CurrentCheckpoint == null) { failureReason = "Fish Warehouse replay has not started."; return false; } if (CurrentCheckpoint.Phase == FishWarehouseReplayPhase.Failed) { failureReason = "Fish Warehouse replay has failed and cannot record inventory restoration."; return false; } HashSet hashSet = (CurrentCheckpoint.InventoryRestoredEmployeeGuids ?? Array.Empty()).ToHashSet(StringComparer.OrdinalIgnoreCase); foreach (string item in (IEnumerable)(((object)employeeGuids) ?? ((object)Array.Empty()))) { if (!Guid.TryParse(item, out var result)) { failureReason = "Fish Warehouse inventory restoration marker contained a malformed employee GUID: " + item; return false; } hashSet.Add(result.ToString("D")); } CurrentCheckpoint = CurrentCheckpoint with { InventoryRestoredEmployeeGuids = hashSet.OrderBy((string guid) => guid, StringComparer.Ordinal).ToArray() }; return true; } public void Fail(FishWarehouseReplayPhase phase, string reason) { if ((object)CurrentCheckpoint != null && CurrentCheckpoint.Phase != FishWarehouseReplayPhase.Failed) { CurrentCheckpoint = CurrentCheckpoint with { Phase = FishWarehouseReplayPhase.Failed, FailurePhase = CurrentCheckpoint.Phase.ToString(), FailureReason = ((phase == CurrentCheckpoint.Phase) ? reason : $"{reason} (reported phase {phase} did not match active phase {CurrentCheckpoint.Phase})") }; } } public bool TryRecoverCaptureFailure(FishWarehouseReplayCheckpoint recoveredCapturedCheckpoint, out string? failureReason) { failureReason = null; FishWarehouseReplayCheckpoint currentCheckpoint = CurrentCheckpoint; if ((object)currentCheckpoint == null || currentCheckpoint.Phase != FishWarehouseReplayPhase.Failed) { failureReason = "Fish Warehouse replay failure is terminal."; return false; } if (!IsReplayCheckpoint(recoveredCapturedCheckpoint) || recoveredCapturedCheckpoint.Phase != FishWarehouseReplayPhase.Captured) { failureReason = "Fish Warehouse recovered checkpoint must be fully persisted at Captured."; return false; } if (!string.Equals(CurrentCheckpoint.GenerationId, recoveredCapturedCheckpoint.GenerationId, StringComparison.Ordinal)) { failureReason = "Fish Warehouse recovered checkpoint used a different generation."; return false; } if (!string.Equals(CurrentCheckpoint.SaveFolderKey, recoveredCapturedCheckpoint.SaveFolderKey, StringComparison.Ordinal)) { failureReason = "Fish Warehouse recovered checkpoint belongs to a different save folder."; return false; } if (!_captureRecoveryPrerequisites(recoveredCapturedCheckpoint).AllSucceeded) { failureReason = "Fish Warehouse capture recovery prerequisites were not all verified."; return false; } LastRecoveredFailureReason = CurrentCheckpoint.FailureReason; CurrentCheckpoint = recoveredCapturedCheckpoint; return true; } public void ResetRuntimeReferences() { } public void ResetForNewCaptureGeneration() { CurrentCheckpoint = null; } private static bool IsReplayCheckpoint(FishWarehouseReplayCheckpoint? checkpoint) { if ((object)checkpoint != null && !string.IsNullOrWhiteSpace(checkpoint.GenerationId) && !string.IsNullOrWhiteSpace(checkpoint.SaveFolderKey)) { FishWarehouseReplayPhase phase = checkpoint.Phase; if (((phase >= FishWarehouseReplayPhase.Captured && phase <= FishWarehouseReplayPhase.Complete) || checkpoint.Phase == FishWarehouseReplayPhase.Failed) && !string.IsNullOrWhiteSpace(checkpoint.SnapshotRelativePath)) { return !string.IsNullOrWhiteSpace(checkpoint.SnapshotSha256); } } return false; } private static FishWarehouseReplayPhase? GetNextPhase(FishWarehouseReplayPhase phase) { return phase switch { FishWarehouseReplayPhase.Captured => FishWarehouseReplayPhase.RuntimeReady, FishWarehouseReplayPhase.RuntimeReady => FishWarehouseReplayPhase.ObjectsReplayed, FishWarehouseReplayPhase.ObjectsReplayed => FishWarehouseReplayPhase.NavigationReady, FishWarehouseReplayPhase.NavigationReady => FishWarehouseReplayPhase.DeliveriesReleased, FishWarehouseReplayPhase.EmployeesReplayed => FishWarehouseReplayPhase.DeliveriesReleased, FishWarehouseReplayPhase.DeliveriesReleased => FishWarehouseReplayPhase.Complete, _ => null, }; } } public static class FishWarehouseSaveCodec { private sealed record V1SaveState(int SchemaVersion, string PropertyCode, bool Unlocked, bool Owned, bool EmployeeHomePlaced = false, string? EmployeeHomeAssignedEmployeeName = null); private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never }; public static string Serialize(FishWarehouseSaveState state) { Validate(state, "Cannot serialize invalid Fish Warehouse state."); return JsonSerializer.Serialize(state, Options); } public static bool TryDeserialize(string json, out FishWarehouseSaveState state, out string? failureReason) { state = null; failureReason = null; if (string.IsNullOrWhiteSpace(json)) { failureReason = "Save state JSON was empty."; return false; } try { using JsonDocument jsonDocument = JsonDocument.Parse(json); JsonElement rootElement = jsonDocument.RootElement; if (rootElement.ValueKind != JsonValueKind.Object) { failureReason = "Save state JSON root was not an object."; return false; } if (!rootElement.TryGetProperty("schemaVersion", out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetInt32(out var _)) { failureReason = "Save state schemaVersion was missing or invalid."; return false; } if (!rootElement.TryGetProperty("propertyCode", out var value3) || value3.ValueKind != JsonValueKind.String) { failureReason = "Save state propertyCode was missing or invalid."; return false; } if (!rootElement.TryGetProperty("unlocked", out var value4) || (value4.ValueKind != JsonValueKind.True && value4.ValueKind != JsonValueKind.False)) { failureReason = "Save state unlocked flag was missing or invalid."; return false; } if (!rootElement.TryGetProperty("owned", out var value5) || (value5.ValueKind != JsonValueKind.True && value5.ValueKind != JsonValueKind.False)) { failureReason = "Save state owned flag was missing or invalid."; return false; } int @int = value.GetInt32(); if (@int == 2) { ValidateV2JsonShape(rootElement); } state = @int switch { 1 => MigrateV1(JsonSerializer.Deserialize(json, Options)), 2 => JsonSerializer.Deserialize(json, Options), _ => throw new InvalidOperationException("Save state schema was not supported."), }; Validate(state, "Save state identity or schema was not supported."); return true; } catch (Exception ex) when (((ex is JsonException || ex is InvalidOperationException || ex is ArgumentException || ex is NotSupportedException) ? 1 : 0) != 0) { state = null; failureReason = ex.Message; return false; } } private static void Validate(FishWarehouseSaveState state, string message) { if ((object)state == null || state.SchemaVersion != 2 || !string.Equals(state.PropertyCode, "oc_fishwarehouse", StringComparison.Ordinal) || (state.Owned && !state.Unlocked) || state.UnsupportedEmployees == null) { throw new InvalidOperationException(message); } HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); foreach (FishWarehouseUnsupportedEmployeeRecord unsupportedEmployee in state.UnsupportedEmployees) { if ((object)unsupportedEmployee == null || string.IsNullOrWhiteSpace(unsupportedEmployee.Guid) || string.IsNullOrWhiteSpace(unsupportedEmployee.DataType) || string.IsNullOrWhiteSpace(unsupportedEmployee.Identity) || unsupportedEmployee.RawJson == null || !hashSet.Add(unsupportedEmployee.Guid)) { throw new InvalidOperationException("Save state contained an invalid or duplicate unsupported employee."); } } if ((object)state.Replay == null) { return; } if (Enum.IsDefined(state.Replay.Phase) && !string.IsNullOrWhiteSpace(state.Replay.GenerationId) && IsSha256(state.Replay.SaveFolderKey) && !string.IsNullOrWhiteSpace(state.Replay.SnapshotRelativePath) && !Path.IsPathRooted(state.Replay.SnapshotRelativePath) && IsSha256(state.Replay.SnapshotSha256) && state.Replay.CapturedObjectCount >= 0 && state.Replay.CapturedEmployeeCount >= 0 && state.Replay.ReplayedObjectCount >= 0 && state.Replay.ReplayedEmployeeCount >= 0) { IReadOnlyList? inventoryRestoredEmployeeGuids = state.Replay.InventoryRestoredEmployeeGuids; if ((inventoryRestoredEmployeeGuids == null || !inventoryRestoredEmployeeGuids.Any((string guid) => !Guid.TryParse(guid, out var _) || string.IsNullOrWhiteSpace(guid))) && (state.Replay.InventoryRestoredEmployeeGuids == null || state.Replay.InventoryRestoredEmployeeGuids.Count == state.Replay.InventoryRestoredEmployeeGuids.Select((string guid) => guid.ToUpperInvariant()).Distinct(StringComparer.Ordinal).Count())) { return; } } throw new InvalidOperationException("Save state replay checkpoint folder or hash fields were invalid."); } private static void ValidateV2JsonShape(JsonElement root) { if (!root.TryGetProperty("employeeHomePlaced", out var value) || (value.ValueKind != JsonValueKind.True && value.ValueKind != JsonValueKind.False)) { throw new InvalidOperationException("Save state employeeHomePlaced was missing or invalid."); } if (!root.TryGetProperty("unsupportedEmployees", out var value2) || value2.ValueKind != JsonValueKind.Array) { throw new InvalidOperationException("Save state unsupportedEmployees was missing or invalid."); } if (root.TryGetProperty("employeeAgentTypeId", out var value3) && value3.ValueKind != JsonValueKind.Null && (value3.ValueKind != JsonValueKind.Number || !value3.TryGetInt32(out var _))) { throw new InvalidOperationException("Save state employeeAgentTypeId was invalid."); } if (root.TryGetProperty("replay", out var value5) && value5.ValueKind != JsonValueKind.Null && value5.ValueKind != JsonValueKind.Object) { throw new InvalidOperationException("Save state replay was invalid."); } } private static FishWarehouseSaveState MigrateV1(V1SaveState? v1) { if ((object)v1 == null) { throw new InvalidOperationException("Save state schema v1 was invalid."); } return new FishWarehouseSaveState(2, v1.PropertyCode, v1.Unlocked, v1.Owned, v1.EmployeeHomePlaced, null, null, Array.Empty()); } private static bool IsSha256(string value) { if (value.Length != 64) { return false; } for (int i = 0; i < value.Length; i++) { if (!Uri.IsHexDigit(value[i])) { return false; } } return true; } } public static class FishWarehouseSaveFolderKey { public static string Create(string saveFolder) { if (string.IsNullOrWhiteSpace(saveFolder)) { throw new ArgumentException("Save folder was empty.", "saveFolder"); } string s = Path.TrimEndingDirectorySeparator(Path.GetFullPath(saveFolder)).ToUpperInvariant(); return Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(s))); } } public static class FishWarehouseSavePath { public static bool TryResolve(string? activeSaveFolder, out string sidecarPath, out string? failureReason) { return TryResolveSidecarPath(activeSaveFolder, out sidecarPath, out failureReason); } public static bool TryResolveSidecarPath(string? activeSaveFolder, out string sidecarPath, out string? failureReason) { return TryResolveUnderSaveFolder(activeSaveFolder, "OrganizedCrime", "fish-warehouse.json", out sidecarPath, out failureReason); } public static bool TryResolveSnapshotPath(string? activeSaveFolder, out string snapshotPath, out string? failureReason) { return TryResolveUnderSaveFolder(activeSaveFolder, "OrganizedCrime", "fish-warehouse.snapshot.json", out snapshotPath, out failureReason); } public static bool TryResolveNativePropertyPath(string? activeSaveFolder, RuntimePropertyDefinition definition, out string propertyPath, out string? failureReason) { propertyPath = string.Empty; if ((object)definition == null) { failureReason = "Runtime Property definition was unavailable."; return false; } if (!TryResolveSaveFolder(activeSaveFolder, out string saveFolder, out failureReason)) { return false; } string text = Path.Combine(saveFolder, "Properties", definition.DisplayName + ".json"); string text2 = Path.Combine(saveFolder, "Properties", definition.NativeName + ".json"); propertyPath = (File.Exists(text) ? text : text2); return true; } private static bool TryResolveUnderSaveFolder(string? activeSaveFolder, string directoryName, string fileName, out string path, out string? failureReason) { path = string.Empty; if (!TryResolveSaveFolder(activeSaveFolder, out string saveFolder, out failureReason)) { return false; } path = Path.Combine(saveFolder, directoryName, fileName); return true; } private static bool TryResolveSaveFolder(string? activeSaveFolder, out string saveFolder, out string? failureReason) { saveFolder = string.Empty; failureReason = null; if (string.IsNullOrWhiteSpace(activeSaveFolder)) { failureReason = "Active Schedule I save folder was unavailable."; return false; } try { saveFolder = Path.GetFullPath(activeSaveFolder.Trim()); if (Directory.Exists(saveFolder)) { return true; } failureReason = "Active Schedule I save folder did not exist: " + saveFolder; return false; } catch (Exception ex) when (((ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException) ? 1 : 0) != 0) { failureReason = "Active Schedule I save folder could not be resolved: " + ex.Message; return false; } } } public sealed record FishWarehouseSaveState(int SchemaVersion, string PropertyCode, bool Unlocked, bool Owned, bool EmployeeHomePlaced, int? EmployeeAgentTypeId, FishWarehouseReplayCheckpoint? Replay, IReadOnlyList UnsupportedEmployees) { public const int CurrentSchemaVersion = 2; public const string ExpectedPropertyCode = "oc_fishwarehouse"; public static FishWarehouseSaveState OwnedState() { return new FishWarehouseSaveState(2, "oc_fishwarehouse", Unlocked: true, Owned: true, EmployeeHomePlaced: false, null, null, Array.Empty()); } public static FishWarehouseSaveState UnownedState() { return new FishWarehouseSaveState(2, "oc_fishwarehouse", Unlocked: false, Owned: false, EmployeeHomePlaced: false, null, null, Array.Empty()); } [CompilerGenerated] private FishWarehouseSaveState(FishWarehouseSaveState original) { SchemaVersion = original.SchemaVersion; PropertyCode = original.PropertyCode; Unlocked = original.Unlocked; Owned = original.Owned; EmployeeHomePlaced = original.EmployeeHomePlaced; EmployeeAgentTypeId = original.EmployeeAgentTypeId; Replay = original.Replay; UnsupportedEmployees = original.UnsupportedEmployees; } } public interface IFishWarehouseSaveStore { bool TryRead(string saveFolder, out FishWarehouseSaveState state, out string? failureReason); bool TryWrite(string saveFolder, FishWarehouseSaveState state, out string? failureReason); } public sealed class FishWarehouseSaveStore : IFishWarehouseSaveStore { public bool TryRead(string saveFolder, out FishWarehouseSaveState state, out string? failureReason) { state = null; failureReason = null; if (!FishWarehouseSavePath.TryResolveSidecarPath(saveFolder, out string sidecarPath, out failureReason)) { return false; } if (!File.Exists(sidecarPath)) { failureReason = "Fish Warehouse sidecar was not found: " + sidecarPath; return false; } try { return FishWarehouseSaveCodec.TryDeserialize(File.ReadAllText(sidecarPath), out state, out failureReason); } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse sidecar could not be read: " + ex.Message; return false; } } public bool TryWrite(string saveFolder, FishWarehouseSaveState state, out string? failureReason) { failureReason = null; if (!FishWarehouseSavePath.TryResolveSidecarPath(saveFolder, out string sidecarPath, out failureReason)) { return false; } string directoryName = Path.GetDirectoryName(sidecarPath); if (string.IsNullOrWhiteSpace(directoryName)) { failureReason = "Fish Warehouse sidecar directory could not be resolved."; return false; } string text = $"{sidecarPath}.{Guid.NewGuid():N}.tmp"; try { Directory.CreateDirectory(directoryName); File.WriteAllText(text, FishWarehouseSaveCodec.Serialize(state)); File.Move(text, sidecarPath, overwrite: true); return true; } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is InvalidOperationException) ? 1 : 0) != 0) { failureReason = "Fish Warehouse sidecar could not be written: " + ex.Message; return false; } finally { try { if (File.Exists(text)) { File.Delete(text); } } catch { } } } } public sealed record FishWarehouseProtectedSnapshot(string GenerationId, string SaveFolderKey, string RelativePath, string Sha256, int ByteCount); public interface IFishWarehousePropertySnapshotStore { bool TryProtect(string saveFolder, string generationId, byte[] bytes, out FishWarehouseProtectedSnapshot snapshot, out string? failureReason); bool TryRead(string saveFolder, FishWarehouseProtectedSnapshot snapshot, out byte[] bytes, out string? failureReason); bool TryRestoreNativeProperty(string saveFolder, FishWarehouseProtectedSnapshot snapshot, out string? failureReason); } public enum LocalPressureCodecFailureReason { None, EmptyJson, MalformedJson, InvalidRoot, MissingRequiredField, InvalidJsonType, InvalidSchemaVersion, MissingPlayers, UnsupportedSchema, InvalidPlayerId, DuplicatePlayerId, InvalidHeat, InvalidGameTime, InvalidRevision, InvalidContext, UnknownField, InvalidEnvelope, SerializationFailed } public sealed record LocalPressureCodecResult(bool Succeeded, LocalPressureCodecFailureReason Reason, string Message) { public static LocalPressureCodecResult Success() { return new LocalPressureCodecResult(Succeeded: true, LocalPressureCodecFailureReason.None, string.Empty); } public static LocalPressureCodecResult Failure(LocalPressureCodecFailureReason reason, string message) { return new LocalPressureCodecResult(Succeeded: false, reason, message); } } public static class LocalPressureSaveCodec { public const int CurrentSchemaVersion = 1; public const int MaximumPlayerIdLength = 256; public const int MaximumContextLength = 64; private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true, DefaultIgnoreCondition = JsonIgnoreCondition.Never }; private static readonly HashSet EnvelopeFields = new HashSet(StringComparer.Ordinal) { "schemaVersion", "players" }; private static readonly HashSet PlayerFields = new HashSet(StringComparer.Ordinal) { "playerId", "localHeat", "knownOffender", "lastEvidenceGameTime", "quietGraceUntil", "lastDecayEvaluation", "region", "property", "revision" }; public static LocalPressureSaveEnvelope CreateEmpty() { return LocalPressureSaveEnvelope.CreateEmpty(); } public static bool TrySerialize(LocalPressureSaveEnvelope? envelope, out string json, out LocalPressureCodecResult result) { json = string.Empty; if (!TryValidateEnvelope(envelope, out result)) { return false; } try { LocalPressureSaveEnvelope value = new LocalPressureSaveEnvelope(1, envelope.Players.OrderBy((LocalPressurePlayerRecord player) => player.PlayerId, StringComparer.Ordinal).ToArray()); json = JsonSerializer.Serialize(value, Options); result = LocalPressureCodecResult.Success(); return true; } catch (Exception ex) when (((ex is JsonException || ex is NotSupportedException || ex is InvalidOperationException) ? 1 : 0) != 0) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.SerializationFailed, "Local Pressure sidecar could not be serialized: " + ex.Message); return false; } } public static bool TrySerialize(IEnumerable? states, out string json, out LocalPressureCodecResult result) { json = string.Empty; if (states == null) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.InvalidEnvelope, "Local Pressure state collection was null."); return false; } try { return TrySerialize(new LocalPressureSaveEnvelope(1, states.Select(LocalPressurePlayerRecord.FromState).ToArray()), out json, out result); } catch (ArgumentException ex) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.InvalidPlayerId, ex.Message); return false; } } public static bool TryDeserialize(string? json, out LocalPressureSaveEnvelope? envelope, out LocalPressureCodecResult result) { envelope = null; result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.MalformedJson, "Local Pressure sidecar JSON was invalid."); if (string.IsNullOrWhiteSpace(json)) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.EmptyJson, "Local Pressure sidecar JSON was empty."); return false; } try { using JsonDocument jsonDocument = JsonDocument.Parse(json); JsonElement rootElement = jsonDocument.RootElement; if (rootElement.ValueKind != JsonValueKind.Object) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.InvalidRoot, "Local Pressure sidecar JSON root was not an object."); return false; } if (!TryValidateUniqueProperties(rootElement, EnvelopeFields, out result)) { return false; } if (!rootElement.TryGetProperty("schemaVersion", out var value)) { return Fail(out result, LocalPressureCodecFailureReason.MissingRequiredField, "Local Pressure schemaVersion was missing."); } if (value.ValueKind != JsonValueKind.Number || !value.TryGetInt32(out var value2)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidSchemaVersion, "Local Pressure schemaVersion was invalid."); } return (value2 != 1) ? Fail(out result, LocalPressureCodecFailureReason.UnsupportedSchema, $"Local Pressure schemaVersion {value2} is not supported.") : TryDeserializeV1(rootElement, out envelope, out result); } catch (JsonException ex) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.MalformedJson, "Local Pressure sidecar JSON was malformed: " + ex.Message); return false; } catch (InvalidOperationException ex2) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.MalformedJson, "Local Pressure sidecar JSON could not be read: " + ex2.Message); return false; } } public static bool TryDeserializeStates(string? json, out IReadOnlyList? states, out LocalPressureCodecResult result) { states = null; if (!TryDeserialize(json, out LocalPressureSaveEnvelope envelope, out result)) { return false; } try { states = envelope.Players.Select((LocalPressurePlayerRecord player) => player.ToState()).ToArray(); return true; } catch (ArgumentException ex) { result = LocalPressureCodecResult.Failure(LocalPressureCodecFailureReason.InvalidEnvelope, ex.Message); return false; } } private static bool TryDeserializeV1(JsonElement root, out LocalPressureSaveEnvelope? envelope, out LocalPressureCodecResult result) { envelope = null; if (!root.TryGetProperty("players", out var value)) { return Fail(out result, LocalPressureCodecFailureReason.MissingPlayers, "Local Pressure players collection was missing."); } if (value.ValueKind != JsonValueKind.Array) { return Fail(out result, LocalPressureCodecFailureReason.MissingPlayers, "Local Pressure players collection was not an array."); } List list = new List(); HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (JsonElement item in value.EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) { return Fail(out result, LocalPressureCodecFailureReason.InvalidJsonType, "Local Pressure player record was not an object."); } if (!TryValidateUniqueProperties(item, PlayerFields, out result)) { return false; } if (!TryReadPlayer(item, out LocalPressurePlayerRecord record, out result)) { return false; } if (!hashSet.Add(record.PlayerId)) { return Fail(out result, LocalPressureCodecFailureReason.DuplicatePlayerId, "Local Pressure player ID was duplicated: " + record.PlayerId + "."); } list.Add(record); } envelope = new LocalPressureSaveEnvelope(1, list); result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadPlayer(JsonElement player, out LocalPressurePlayerRecord? record, out LocalPressureCodecResult result) { record = null; if (!TryReadRequiredString(player, "playerId", out string value, out result)) { return false; } if (!IsValidIdentity(value)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidPlayerId, "Local Pressure playerId was blank, unbounded, or contained control characters."); } if (!TryReadRequiredInt32(player, "localHeat", out var value2, out result)) { return false; } if (value2 < 0 || value2 > 100) { return Fail(out result, LocalPressureCodecFailureReason.InvalidHeat, "Local Pressure localHeat was outside 0 through 100."); } if (!TryReadRequiredBoolean(player, "knownOffender", out var value3, out result)) { return false; } if (!TryReadGameTime(player, "lastEvidenceGameTime", out var value4, out result) || !TryReadGameTime(player, "quietGraceUntil", out var value5, out result) || !TryReadGameTime(player, "lastDecayEvaluation", out var value6, out result)) { return false; } if (!TryReadOptionalContext(player, "region", out string value7, out result) || !TryReadOptionalContext(player, "property", out string value8, out result)) { return false; } if (!TryReadRequiredInt64(player, "revision", out var value9, out result)) { return false; } if (value9 < 0) { return Fail(out result, LocalPressureCodecFailureReason.InvalidRevision, "Local Pressure revision cannot be negative."); } record = new LocalPressurePlayerRecord(value, value2, value3, value4, value5, value6, value7, value8, value9); result = LocalPressureCodecResult.Success(); return true; } private static bool TryValidateEnvelope(LocalPressureSaveEnvelope? envelope, out LocalPressureCodecResult result) { if ((object)envelope == null) { return Fail(out result, LocalPressureCodecFailureReason.InvalidEnvelope, "Local Pressure envelope was null."); } if (envelope.SchemaVersion != 1) { return Fail(out result, LocalPressureCodecFailureReason.UnsupportedSchema, "Local Pressure envelope schema was not supported."); } if (envelope.Players == null) { return Fail(out result, LocalPressureCodecFailureReason.MissingPlayers, "Local Pressure players collection was null."); } HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (LocalPressurePlayerRecord player in envelope.Players) { if ((object)player == null) { return Fail(out result, LocalPressureCodecFailureReason.InvalidEnvelope, "Local Pressure player record was null."); } if (!IsValidIdentity(player.PlayerId)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidPlayerId, "Local Pressure playerId was blank, unbounded, or contained control characters."); } if (!hashSet.Add(player.PlayerId)) { return Fail(out result, LocalPressureCodecFailureReason.DuplicatePlayerId, "Local Pressure player ID was duplicated: " + player.PlayerId + "."); } if (player.LocalHeat < 0 || player.LocalHeat > 100) { return Fail(out result, LocalPressureCodecFailureReason.InvalidHeat, "Local Pressure localHeat was outside 0 through 100."); } if (!IsValidGameTime(player.LastEvidenceGameTime) || !IsValidGameTime(player.QuietGraceUntil) || !IsValidGameTime(player.LastDecayEvaluation)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidGameTime, "Local Pressure game time was invalid."); } if (player.Revision < 0) { return Fail(out result, LocalPressureCodecFailureReason.InvalidRevision, "Local Pressure revision cannot be negative."); } if (!IsValidContext(player.Region) || !IsValidContext(player.Property)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidContext, "Local Pressure presentation context was invalid or unbounded."); } } result = LocalPressureCodecResult.Success(); return true; } private static bool TryValidateUniqueProperties(JsonElement element, HashSet allowedFields, out LocalPressureCodecResult result) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (JsonProperty item in element.EnumerateObject()) { if (!hashSet.Add(item.Name)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidEnvelope, "Local Pressure JSON field was duplicated: " + item.Name + "."); } if (!allowedFields.Contains(item.Name)) { return Fail(out result, LocalPressureCodecFailureReason.UnknownField, "Local Pressure JSON field was unsupported: " + item.Name + "."); } } result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadRequiredString(JsonElement element, string name, out string? value, out LocalPressureCodecResult result) { value = null; if (!element.TryGetProperty(name, out var value2)) { return Fail(out result, LocalPressureCodecFailureReason.MissingRequiredField, "Local Pressure field " + name + " was missing."); } if (value2.ValueKind != JsonValueKind.String) { return Fail(out result, LocalPressureCodecFailureReason.InvalidJsonType, "Local Pressure field " + name + " was not a string."); } value = value2.GetString(); result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadRequiredInt32(JsonElement element, string name, out int value, out LocalPressureCodecResult result) { value = 0; if (!element.TryGetProperty(name, out var value2)) { return Fail(out result, LocalPressureCodecFailureReason.MissingRequiredField, "Local Pressure field " + name + " was missing."); } if (value2.ValueKind != JsonValueKind.Number || !value2.TryGetInt32(out value)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidJsonType, "Local Pressure field " + name + " was not an integer."); } result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadRequiredInt64(JsonElement element, string name, out long value, out LocalPressureCodecResult result) { value = 0L; if (!element.TryGetProperty(name, out var value2)) { return Fail(out result, LocalPressureCodecFailureReason.MissingRequiredField, "Local Pressure field " + name + " was missing."); } if (value2.ValueKind != JsonValueKind.Number || !value2.TryGetInt64(out value)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidJsonType, "Local Pressure field " + name + " was not an integer."); } result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadRequiredBoolean(JsonElement element, string name, out bool value, out LocalPressureCodecResult result) { value = false; if (!element.TryGetProperty(name, out var value2)) { return Fail(out result, LocalPressureCodecFailureReason.MissingRequiredField, "Local Pressure field " + name + " was missing."); } JsonValueKind valueKind = value2.ValueKind; if (valueKind - 5 > JsonValueKind.Object) { return Fail(out result, LocalPressureCodecFailureReason.InvalidJsonType, "Local Pressure field " + name + " was not a boolean."); } value = value2.GetBoolean(); result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadGameTime(JsonElement element, string name, out double? value, out LocalPressureCodecResult result) { value = null; if (!element.TryGetProperty(name, out var value2)) { return Fail(out result, LocalPressureCodecFailureReason.MissingRequiredField, "Local Pressure field " + name + " was missing."); } if (value2.ValueKind == JsonValueKind.Null) { result = LocalPressureCodecResult.Success(); return true; } if (value2.ValueKind != JsonValueKind.Number || !value2.TryGetDouble(out var value3) || !IsValidGameTime(value3)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidGameTime, "Local Pressure field " + name + " was not a finite, non-negative game time."); } value = value3; result = LocalPressureCodecResult.Success(); return true; } private static bool TryReadOptionalContext(JsonElement element, string name, out string? value, out LocalPressureCodecResult result) { value = null; if (!element.TryGetProperty(name, out var value2)) { result = LocalPressureCodecResult.Success(); return true; } if (value2.ValueKind == JsonValueKind.Null) { result = LocalPressureCodecResult.Success(); return true; } if (value2.ValueKind != JsonValueKind.String) { return Fail(out result, LocalPressureCodecFailureReason.InvalidContext, "Local Pressure field " + name + " was not a string or null."); } value = value2.GetString(); if (!IsValidContext(value)) { return Fail(out result, LocalPressureCodecFailureReason.InvalidContext, "Local Pressure field " + name + " was blank, unbounded, or contained control characters."); } result = LocalPressureCodecResult.Success(); return true; } private static bool IsValidIdentity(string value) { if (!string.IsNullOrWhiteSpace(value) && value.Length <= 256) { return value.All((char character) => !char.IsControl(character)); } return false; } private static bool IsValidContext(string? value) { if (value != null) { if (!string.IsNullOrWhiteSpace(value) && value.Length <= 64) { return value.All((char character) => !char.IsControl(character)); } return false; } return true; } private static bool IsValidGameTime(double? value) { if (value.HasValue) { if (!double.IsNaN(value.Value) && !double.IsInfinity(value.Value)) { return value.Value >= 0.0; } return false; } return true; } private static bool Fail(out LocalPressureCodecResult result, LocalPressureCodecFailureReason reason, string message) { result = LocalPressureCodecResult.Failure(reason, message); return false; } } public sealed record LocalPressureSaveEnvelope(int SchemaVersion, IReadOnlyList Players) { public static LocalPressureSaveEnvelope CreateEmpty() { return new LocalPressureSaveEnvelope(1, Array.Empty()); } } public sealed record LocalPressurePlayerRecord(string PlayerId, int LocalHeat, bool KnownOffender, double? LastEvidenceGameTime, double? QuietGraceUntil, double? LastDecayEvaluation, string? Region, string? Property, long Revision) { public static LocalPressurePlayerRecord CreateDefault(string playerId) { return new LocalPressurePlayerRecord(playerId, 0, KnownOffender: false, null, null, null, null, null, 0L); } public static LocalPressurePlayerRecord FromState(LocalPressureState state) { ArgumentNullException.ThrowIfNull(state, "state"); if (string.IsNullOrWhiteSpace(state.PlayerId)) { throw new ArgumentException("Persisted Local Pressure state requires a player identity.", "state"); } return new LocalPressurePlayerRecord(state.PlayerId, state.LocalHeat, state.KnownOffender, state.LastEvidenceGameTime, state.QuietGraceUntil, state.LastDecayEvaluation, state.Region, state.PropertyCode, state.Revision); } public LocalPressureState ToState() { return new LocalPressureState(LocalHeat, KnownOffender, LastEvidenceGameTime, QuietGraceUntil, LastDecayEvaluation, PlayerId, Region, Property, Revision); } } public enum LocalPressureSavePathFailureReason { None, ActiveSaveFolderMissing, InvalidActiveSaveFolder } public sealed record LocalPressureSavePathResult(bool Succeeded, LocalPressureSavePathFailureReason Reason, string Message) { public static LocalPressureSavePathResult Success() { return new LocalPressureSavePathResult(Succeeded: true, LocalPressureSavePathFailureReason.None, string.Empty); } public static LocalPressureSavePathResult Failure(LocalPressureSavePathFailureReason reason, string message) { return new LocalPressureSavePathResult(Succeeded: false, reason, message); } } public sealed class LocalPressureSavePath { public string ActiveSaveFolder { get; } public string SidecarDirectory { get; } public string SidecarFilePath { get; } private LocalPressureSavePath(string activeSaveFolder) { ActiveSaveFolder = activeSaveFolder; SidecarDirectory = Path.Combine(activeSaveFolder, "OrganizedCrime"); SidecarFilePath = Path.Combine(SidecarDirectory, "local-pressure.json"); } public static bool TryCreate(string? activeSaveFolder, out LocalPressureSavePath? path, out LocalPressureSavePathResult result) { path = null; if (string.IsNullOrWhiteSpace(activeSaveFolder)) { result = LocalPressureSavePathResult.Failure(LocalPressureSavePathFailureReason.InvalidActiveSaveFolder, "Active Schedule I save folder was empty."); return false; } try { string fullPath = Path.GetFullPath(activeSaveFolder.Trim()); if (!Directory.Exists(fullPath)) { result = LocalPressureSavePathResult.Failure(LocalPressureSavePathFailureReason.ActiveSaveFolderMissing, "Active Schedule I save folder did not exist: " + fullPath); return false; } path = new LocalPressureSavePath(fullPath); result = LocalPressureSavePathResult.Success(); return true; } catch (Exception ex) when (((ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException) ? 1 : 0) != 0) { result = LocalPressureSavePathResult.Failure(LocalPressureSavePathFailureReason.InvalidActiveSaveFolder, "Active Schedule I save folder could not be resolved: " + ex.Message); return false; } } } public enum LocalPressureStoreLoadStatus { Loaded, Empty, Failed } public enum LocalPressureStoreFailureReason { None, SidecarReadFailed, EmptyOrMalformedJson, InvalidRoot, MissingRequiredField, InvalidJsonType, InvalidSchemaVersion, MissingPlayers, UnsupportedSchema, InvalidPlayerId, DuplicatePlayerId, InvalidHeat, InvalidGameTime, InvalidRevision, InvalidContext, InvalidCurrentState, InvalidIncomingRecord, StaleRevision, RevisionConflict, SerializationFailed, DirectoryCreationFailed, TemporaryFileWriteFailed, AtomicReplacementFailed } public enum LocalPressureStoreUpdateStatus { Updated, Idempotent, Rejected } public sealed record LocalPressureStoreLoadResult(bool Succeeded, LocalPressureStoreLoadStatus Status, LocalPressureSaveEnvelope? Envelope, LocalPressureStoreFailureReason FailureReason, string Message); public sealed record LocalPressureStoreUpdateResult(bool Succeeded, LocalPressureStoreUpdateStatus Status, LocalPressureSaveEnvelope? Envelope, LocalPressureStoreFailureReason FailureReason, string Message); public interface ILocalPressureFileSystem { bool FileExists(string path); string ReadAllText(string path); void CreateDirectory(string path); string CreateTemporaryPath(string directory, string targetPath); void WriteAllTextAndFlush(string path, string contents); void ReplaceAtomically(string temporaryPath, string targetPath); void DeleteFile(string path); } public sealed class LocalPressureStateStore { private readonly record struct WriteFailure(LocalPressureStoreFailureReason Reason, string Message) { public static WriteFailure None => new WriteFailure(LocalPressureStoreFailureReason.None, string.Empty); } private static readonly ConcurrentDictionary Locks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly ILocalPressureFileSystem _fileSystem; public LocalPressureSavePath SavePath { get; } public LocalPressureStateStore(LocalPressureSavePath savePath, ILocalPressureFileSystem? fileSystem = null) { SavePath = savePath ?? throw new ArgumentNullException("savePath"); _fileSystem = fileSystem ?? new LocalPressureFileSystem(); } public bool TryLoad(out LocalPressureStoreLoadResult result) { lock (GetLock(SavePath.SidecarFilePath)) { result = TryLoadLocked(); return result.Succeeded; } } public bool TryUpdate(LocalPressurePlayerRecord incoming, out LocalPressureStoreUpdateResult result) { lock (GetLock(SavePath.SidecarFilePath)) { if (!TryValidateIncoming(incoming, out LocalPressureCodecResult result2)) { result = new LocalPressureStoreUpdateResult(Succeeded: false, LocalPressureStoreUpdateStatus.Rejected, null, LocalPressureStoreFailureReason.InvalidIncomingRecord, result2.Message); return false; } LocalPressureStoreLoadResult localPressureStoreLoadResult = TryLoadLocked(); if (!localPressureStoreLoadResult.Succeeded) { result = new LocalPressureStoreUpdateResult(Succeeded: false, LocalPressureStoreUpdateStatus.Rejected, null, localPressureStoreLoadResult.FailureReason, localPressureStoreLoadResult.Message); return false; } LocalPressureSaveEnvelope envelope = localPressureStoreLoadResult.Envelope; LocalPressurePlayerRecord localPressurePlayerRecord = envelope.Players.FirstOrDefault((LocalPressurePlayerRecord player) => string.Equals(player.PlayerId, incoming.PlayerId, StringComparison.Ordinal)); if ((object)localPressurePlayerRecord != null) { if (incoming.Revision < localPressurePlayerRecord.Revision) { return Reject(LocalPressureStoreFailureReason.StaleRevision, "Incoming Local Pressure revision was stale.", out result); } if (incoming.Revision == localPressurePlayerRecord.Revision) { if (incoming == localPressurePlayerRecord) { result = new LocalPressureStoreUpdateResult(Succeeded: true, LocalPressureStoreUpdateStatus.Idempotent, envelope, LocalPressureStoreFailureReason.None, "Incoming Local Pressure update was already stored."); return true; } return Reject(LocalPressureStoreFailureReason.RevisionConflict, "Incoming Local Pressure revision conflicted with stored state.", out result); } } LocalPressurePlayerRecord[] players = envelope.Players.Where((LocalPressurePlayerRecord player) => !string.Equals(player.PlayerId, incoming.PlayerId, StringComparison.Ordinal)).Append(incoming).ToArray(); LocalPressureSaveEnvelope envelope2 = new LocalPressureSaveEnvelope(1, players); if (!LocalPressureSaveCodec.TrySerialize(envelope2, out string json, out LocalPressureCodecResult result3)) { result = new LocalPressureStoreUpdateResult(Succeeded: false, LocalPressureStoreUpdateStatus.Rejected, null, LocalPressureStoreFailureReason.SerializationFailed, result3.Message); return false; } if (!TryWriteAtomically(json, out var failure)) { result = new LocalPressureStoreUpdateResult(Succeeded: false, LocalPressureStoreUpdateStatus.Rejected, null, failure.Reason, failure.Message); return false; } result = new LocalPressureStoreUpdateResult(Succeeded: true, LocalPressureStoreUpdateStatus.Updated, envelope2, LocalPressureStoreFailureReason.None, "Local Pressure update was stored."); return true; } } private LocalPressureStoreLoadResult TryLoadLocked() { if (!_fileSystem.FileExists(SavePath.SidecarFilePath)) { return new LocalPressureStoreLoadResult(Succeeded: true, LocalPressureStoreLoadStatus.Empty, LocalPressureSaveEnvelope.CreateEmpty(), LocalPressureStoreFailureReason.None, "Local Pressure sidecar was not found; using an empty v1 envelope."); } string json; try { json = _fileSystem.ReadAllText(SavePath.SidecarFilePath); } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is InvalidOperationException) ? 1 : 0) != 0) { return FailedLoad(LocalPressureStoreFailureReason.SidecarReadFailed, "Local Pressure sidecar could not be read: " + ex.Message); } if (!LocalPressureSaveCodec.TryDeserialize(json, out LocalPressureSaveEnvelope envelope, out LocalPressureCodecResult result)) { return FailedLoad(MapCodecFailure(result.Reason), result.Message); } return new LocalPressureStoreLoadResult(Succeeded: true, LocalPressureStoreLoadStatus.Loaded, envelope, LocalPressureStoreFailureReason.None, "Local Pressure sidecar was loaded."); } private bool TryWriteAtomically(string json, out WriteFailure failure) { string text = null; try { try { _fileSystem.CreateDirectory(SavePath.SidecarDirectory); } catch (Exception ex) when (((ex is IOException || ex is UnauthorizedAccessException || ex is InvalidOperationException) ? 1 : 0) != 0) { failure = new WriteFailure(LocalPressureStoreFailureReason.DirectoryCreationFailed, "Local Pressure sidecar directory could not be created: " + ex.Message); return false; } text = _fileSystem.CreateTemporaryPath(SavePath.SidecarDirectory, SavePath.SidecarFilePath); try { _fileSystem.WriteAllTextAndFlush(text, json); } catch (Exception ex2) when (((ex2 is IOException || ex2 is UnauthorizedAccessException || ex2 is InvalidOperationException) ? 1 : 0) != 0) { failure = new WriteFailure(LocalPressureStoreFailureReason.TemporaryFileWriteFailed, "Local Pressure temporary sidecar could not be written: " + ex2.Message); return false; } try { _fileSystem.ReplaceAtomically(text, SavePath.SidecarFilePath); } catch (Exception ex3) when (((ex3 is IOException || ex3 is UnauthorizedAccessException || ex3 is InvalidOperationException || ex3 is PlatformNotSupportedException) ? 1 : 0) != 0) { failure = new WriteFailure(LocalPressureStoreFailureReason.AtomicReplacementFailed, "Local Pressure sidecar could not be replaced atomically: " + ex3.Message); return false; } failure = WriteFailure.None; return true; } catch (Exception ex4) when (((ex4 is IOException || ex4 is UnauthorizedAccessException || ex4 is InvalidOperationException || ex4 is PlatformNotSupportedException) ? 1 : 0) != 0) { failure = new WriteFailure(LocalPressureStoreFailureReason.TemporaryFileWriteFailed, "Local Pressure sidecar write failed: " + ex4.Message); return false; } finally { if (text != null) { try { _fileSystem.DeleteFile(text); } catch (Exception ex5) when (((ex5 is IOException || ex5 is UnauthorizedAccessException || ex5 is InvalidOperationException) ? 1 : 0) != 0) { } } } } private static bool TryValidateIncoming(LocalPressurePlayerRecord? incoming, out LocalPressureCodecResult result) { string json; return LocalPressureSaveCodec.TrySerialize(new LocalPressureSaveEnvelope(1, new LocalPressurePlayerRecord[1] { incoming }), out json, out result); } private static bool Reject(LocalPressureStoreFailureReason reason, string message, out LocalPressureStoreUpdateResult result) { result = new LocalPressureStoreUpdateResult(Succeeded: false, LocalPressureStoreUpdateStatus.Rejected, null, reason, message); return false; } private static LocalPressureStoreLoadResult FailedLoad(LocalPressureStoreFailureReason reason, string message) { return new LocalPressureStoreLoadResult(Succeeded: false, LocalPressureStoreLoadStatus.Failed, null, reason, message); } private static LocalPressureStoreFailureReason MapCodecFailure(LocalPressureCodecFailureReason reason) { switch (reason) { case LocalPressureCodecFailureReason.EmptyJson: case LocalPressureCodecFailureReason.MalformedJson: return LocalPressureStoreFailureReason.EmptyOrMalformedJson; case LocalPressureCodecFailureReason.InvalidRoot: return LocalPressureStoreFailureReason.InvalidRoot; case LocalPressureCodecFailureReason.MissingRequiredField: return LocalPressureStoreFailureReason.MissingRequiredField; case LocalPressureCodecFailureReason.InvalidJsonType: return LocalPressureStoreFailureReason.InvalidJsonType; case LocalPressureCodecFailureReason.InvalidSchemaVersion: return LocalPressureStoreFailureReason.InvalidSchemaVersion; case LocalPressureCodecFailureReason.MissingPlayers: return LocalPressureStoreFailureReason.MissingPlayers; case LocalPressureCodecFailureReason.UnsupportedSchema: return LocalPressureStoreFailureReason.UnsupportedSchema; case LocalPressureCodecFailureReason.InvalidPlayerId: return LocalPressureStoreFailureReason.InvalidPlayerId; case LocalPressureCodecFailureReason.DuplicatePlayerId: return LocalPressureStoreFailureReason.DuplicatePlayerId; case LocalPressureCodecFailureReason.InvalidHeat: return LocalPressureStoreFailureReason.InvalidHeat; case LocalPressureCodecFailureReason.InvalidGameTime: return LocalPressureStoreFailureReason.InvalidGameTime; case LocalPressureCodecFailureReason.InvalidRevision: return LocalPressureStoreFailureReason.InvalidRevision; case LocalPressureCodecFailureReason.InvalidContext: return LocalPressureStoreFailureReason.InvalidContext; default: return LocalPressureStoreFailureReason.InvalidCurrentState; } } private static object GetLock(string path) { return Locks.GetOrAdd(Path.GetFullPath(path), (string _) => new object()); } } internal sealed class LocalPressureFileSystem : ILocalPressureFileSystem { public bool FileExists(string path) { return File.Exists(path); } public string ReadAllText(string path) { return File.ReadAllText(path); } public void CreateDirectory(string path) { Directory.CreateDirectory(path); } public string CreateTemporaryPath(string directory, string targetPath) { return Path.Combine(directory, $".{Path.GetFileName(targetPath)}.{Guid.NewGuid():N}.tmp"); } public void WriteAllTextAndFlush(string path, string contents) { using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(contents); streamWriter.Flush(); fileStream.Flush(flushToDisk: true); } public void ReplaceAtomically(string temporaryPath, string targetPath) { if (File.Exists(targetPath)) { File.Replace(temporaryPath, targetPath, null, ignoreMetadataErrors: true); } else { File.Move(temporaryPath, targetPath); } } public void DeleteFile(string path) { if (File.Exists(path)) { File.Delete(path); } } } public enum Release1StoryCodecFailureReason { None, EmptyJson, MalformedJson, InvalidRoot, UnknownField, MissingRequiredField, InvalidJsonType, InvalidSchemaVersion, UnsupportedSchema, InvalidStory, InvalidMission, InvalidEffect, DuplicateId, SerializationFailed } public sealed record Release1StoryCodecResult(bool Succeeded, Release1StoryCodecFailureReason Reason, string Message) { public static Release1StoryCodecResult Success() { return new Release1StoryCodecResult(Succeeded: true, Release1StoryCodecFailureReason.None, string.Empty); } public static Release1StoryCodecResult Failure(Release1StoryCodecFailureReason reason, string message) { return new Release1StoryCodecResult(Succeeded: false, reason, message); } } public static class Release1StorySaveCodec { private sealed class EnvelopeDto { public int SchemaVersion { get; set; } public StoryDto? Story { get; set; } } private sealed class StoryDto { public string? PlayerId { get; set; } public int Standing { get; set; } public Release1RelationshipState RelationshipState { get; set; } public bool Release1Recognized { get; set; } public string[]? IntroLogicalCorrelationIds { get; set; } public string[]? RecognitionLogicalCorrelationIds { get; set; } public MissionDto[]? Missions { get; set; } public EffectDto[]? NativeEffects { get; set; } public Release1PhonePresentationAttemptDto[]? PhonePresentationAttempts { get; set; } public SmallCourtesyAssignmentDto[]? SmallCourtesyAssignments { get; set; } public PresentationReceiptDto[]? PresentationReceipts { get; set; } public WrongAddressAssignmentDto[]? WrongAddressAssignments { get; set; } public WrongAddressProgressDto[]? WrongAddressProgress { get; set; } public RoomWithNoNameAssignmentDto[]? RoomWithNoNameAssignments { get; set; } public RoomWithNoNameProgressDto[]? RoomWithNoNameProgress { get; set; } public ShortNoticeAssignmentDto[]? ShortNoticeAssignments { get; set; } public ShortNoticeProgressDto[]? ShortNoticeProgress { get; set; } public KeepTheLightsOffAssignmentDto[]? KeepTheLightsOffAssignments { get; set; } public KeepTheLightsOffProgressDto[]? KeepTheLightsOffProgress { get; set; } public TheEnvelopeAssignmentDto[]? TheEnvelopeAssignments { get; set; } public TheEnvelopeProgressDto[]? TheEnvelopeProgress { get; set; } public ChiefRecordDto? ChiefRecord { get; set; } public long Revision { get; set; } } private sealed class MissionDto { public string? MissionKey { get; set; } public Release1MissionState State { get; set; } public int Attempt { get; set; } public string? TermsVersion { get; set; } public double? AcceptedGameTimeHours { get; set; } public double? DeadlineGameTimeHours { get; set; } public Release1MissionOutcome LastOutcome { get; set; } public int StandingPenaltyApplied { get; set; } public string[]? PenaltyReceiptIds { get; set; } public int MakeGoodFailures { get; set; } public Release1RecoveryMode RecoveryMode { get; set; } public string? RewardAuthorizationReceiptId { get; set; } public bool QuietConditionAwarded { get; set; } public string[]? NativeEffectIds { get; set; } public string[]? PresentationCorrelationIds { get; set; } public string[]? AcceptedLogicalCorrelations { get; set; } public string? NativePresentationRef { get; set; } public long Revision { get; set; } } private sealed class Release1PhonePresentationAttemptDto { public string? CorrelationId { get; set; } public string? MissionKey { get; set; } public int Attempt { get; set; } public string? Role { get; set; } public string? RequiredPriorCorrelationId { get; set; } public Release1PhonePresentationAttemptState State { get; set; } public long Revision { get; set; } } private sealed class SmallCourtesyAssignmentDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public Release1SmallCourtesyAssignmentMode Mode { get; set; } public string? AuthorizationCorrelationId { get; set; } public string? ProductId { get; set; } public string? ProductName { get; set; } public double SelectionAskingPrice { get; set; } public string? PackagingId { get; set; } public string? PackagingName { get; set; } public string? DeadDropGuid { get; set; } public string? DeadDropName { get; set; } public string? DeadDropDescription { get; set; } public double DeadDropX { get; set; } public double DeadDropY { get; set; } public double DeadDropZ { get; set; } public double RewardMultiplier { get; set; } } private sealed class PresentationReceiptDto { public string? CorrelationId { get; set; } public long Revision { get; set; } } private sealed class WrongAddressAssignmentDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public Release1WrongAddressAssignmentMode Mode { get; set; } public string? AuthorizationCorrelationId { get; set; } public string? ProductId { get; set; } public string? ProductName { get; set; } public string? PackagingId { get; set; } public string? PackagingName { get; set; } public int PackageQuantity { get; set; } public string? SourceDropGuid { get; set; } public string? SourceDropName { get; set; } public string? SourceDropDescription { get; set; } public double SourceDropX { get; set; } public double SourceDropY { get; set; } public double SourceDropZ { get; set; } public string? HandoffDropGuid { get; set; } public string? HandoffDropName { get; set; } public string? HandoffDropDescription { get; set; } public double HandoffDropX { get; set; } public double HandoffDropY { get; set; } public double HandoffDropZ { get; set; } public double RewardMultiplier { get; set; } } private sealed class WrongAddressProgressDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public bool Staged { get; set; } public bool Custody { get; set; } } private sealed class RoomWithNoNameAssignmentDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public Release1RoomWithNoNameAssignmentMode Mode { get; set; } public string? AuthorizationCorrelationId { get; set; } public string? ProductId { get; set; } public string? ProductName { get; set; } public string? PackagingId { get; set; } public string? PackagingName { get; set; } public int PackageQuantity { get; set; } public string? SourceDropGuid { get; set; } public string? SourceDropName { get; set; } public string? SourceDropDescription { get; set; } public double SourceDropX { get; set; } public double SourceDropY { get; set; } public double SourceDropZ { get; set; } public string? HandoffDropGuid { get; set; } public string? HandoffDropName { get; set; } public string? HandoffDropDescription { get; set; } public double HandoffDropX { get; set; } public double HandoffDropY { get; set; } public double HandoffDropZ { get; set; } public string? HoldRoomKey { get; set; } public int ExpectedClosetCount { get; set; } public double HoldDurationGameMinutes { get; set; } public double RewardMultiplier { get; set; } } private sealed class RoomWithNoNameProgressDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public bool Staged { get; set; } public bool Custody { get; set; } public bool Stowed { get; set; } public bool HoldSatisfied { get; set; } public double? StowedAtGameMinutes { get; set; } public string? HoldingClosetGuid { get; set; } public double? MissingSincePassGameMinutes { get; set; } } private sealed class ShortNoticeAssignmentDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public Release1ShortNoticeAssignmentMode Mode { get; set; } public string? AuthorizationCorrelationId { get; set; } public string? ProductId { get; set; } public string? ProductName { get; set; } public string? PackagingId { get; set; } public string? PackagingName { get; set; } public int RequiredQuantity { get; set; } public string? HandoffDropGuid { get; set; } public string? HandoffDropName { get; set; } public string? HandoffDropDescription { get; set; } public double HandoffDropX { get; set; } public double HandoffDropY { get; set; } public double HandoffDropZ { get; set; } public double? DeadlineGameMinutes { get; set; } public double RewardMultiplier { get; set; } public Release1ShortNoticeValueConvention ValueConvention { get; set; } } private sealed class ShortNoticeProgressDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public int ObservedQuantity { get; set; } public int? LastShortfallNoticed { get; set; } public bool SpreadNoticed { get; set; } } private sealed class KeepTheLightsOffAssignmentDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public Release1KeepTheLightsOffAssignmentMode Mode { get; set; } public string? AuthorizationCorrelationId { get; set; } public int ExpectedOwnedPropertyCount { get; set; } public double WindowDurationGameMinutes { get; set; } public double? DeadlineGameMinutes { get; set; } } private sealed class KeepTheLightsOffProgressDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public double? ClearConfirmedAtGameMinutes { get; set; } public double? BreachSincePassGameMinutes { get; set; } } private sealed class TheEnvelopeAssignmentDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public Release1TheEnvelopeAssignmentMode Mode { get; set; } public string? AuthorizationCorrelationId { get; set; } public double AmountWholeDollars { get; set; } public string? HoldRoomKey { get; set; } public int ExpectedClosetCount { get; set; } public double? DeadlineGameMinutes { get; set; } } private sealed class TheEnvelopeProgressDto { public string? MissionKey { get; set; } public int Attempt { get; set; } public double ObservedBalance { get; set; } public double? LastShortfallNoticed { get; set; } public bool SpreadNoticed { get; set; } } private sealed class ChiefRecordDto { public bool Adopted { get; set; } public LocalPressureTier AdoptedTier { get; set; } public bool AdoptedKnownOffender { get; set; } public int DemandRound { get; set; } public Release1ChiefState State { get; set; } public int WatchLinesSent { get; set; } public int LockdownAnnouncements { get; set; } public bool LockdownEngaged { get; set; } public int PaidLifts { get; set; } public int CooledLifts { get; set; } public int DeclineRepliesSent { get; set; } public int PaymentBlockedNotices { get; set; } public double? LastShortfallNoticed { get; set; } public string[]? AcceptedLogicalCorrelations { get; set; } public string[]? NativeEffectIds { get; set; } public long Revision { get; set; } } private sealed class EffectDto { public string? EffectId { get; set; } public string? MissionKey { get; set; } public int Attempt { get; set; } public string? EffectKind { get; set; } public string? SourceIdentity { get; set; } public string? DestinationIdentity { get; set; } public string? AmountOrCargoIdentity { get; set; } public Release1NativeEffectPhase Phase { get; set; } public string? NativeReceiptId { get; set; } public long PreparedStoryRevision { get; set; } public string? CommittedStoryCorrelationId { get; set; } public string? AuthorizedStoryCorrelationId { get; set; } public bool ExecutionBlocked { get; set; } public long AuthorizedMissionRevision { get; set; } } public const int CurrentSchemaVersion = 11; private static readonly HashSet SupportedReadSchemaVersions = new HashSet { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, WriteIndented = true, Converters = { (JsonConverter)new JsonStringEnumConverter(null, allowIntegerValues: false) } }; private static readonly HashSet EnvelopeFields = Set("schemaVersion", "story"); private static readonly HashSet StoryFields = Set("playerId", "standing", "relationshipState", "release1Recognized", "introLogicalCorrelationIds", "recognitionLogicalCorrelationIds", "missions", "nativeEffects", "phonePresentationAttempts", "smallCourtesyAssignments", "presentationReceipts", "wrongAddressAssignments", "wrongAddressProgress", "roomWithNoNameAssignments", "roomWithNoNameProgress", "shortNoticeAssignments", "shortNoticeProgress", "keepTheLightsOffAssignments", "keepTheLightsOffProgress", "theEnvelopeAssignments", "theEnvelopeProgress", "chiefRecord", "revision"); private static readonly HashSet RequiredStoryFields = Set("playerId", "standing", "relationshipState", "release1Recognized", "introLogicalCorrelationIds", "recognitionLogicalCorrelationIds", "missions", "nativeEffects", "revision"); private static readonly HashSet MissionFields = Set("missionKey", "state", "attempt", "termsVersion", "acceptedGameTimeHours", "deadlineGameTimeHours", "lastOutcome", "standingPenaltyApplied", "penaltyReceiptIds", "makeGoodFailures", "recoveryMode", "rewardAuthorizationReceiptId", "quietConditionAwarded", "nativeEffectIds", "presentationCorrelationIds", "acceptedLogicalCorrelations", "nativePresentationRef", "revision"); private static readonly HashSet EffectFields = Set("effectId", "missionKey", "attempt", "effectKind", "sourceIdentity", "destinationIdentity", "amountOrCargoIdentity", "phase", "nativeReceiptId", "preparedStoryRevision", "committedStoryCorrelationId", "authorizedStoryCorrelationId", "executionBlocked", "authorizedMissionRevision"); private static readonly HashSet PhonePresentationAttemptFields = Set("correlationId", "missionKey", "attempt", "role", "requiredPriorCorrelationId", "state", "revision"); private static readonly HashSet SmallCourtesyAssignmentFields = Set("missionKey", "attempt", "mode", "authorizationCorrelationId", "productId", "productName", "selectionAskingPrice", "packagingId", "packagingName", "deadDropGuid", "deadDropName", "deadDropDescription", "deadDropX", "deadDropY", "deadDropZ", "rewardMultiplier"); private static readonly HashSet PresentationReceiptFields = Set("correlationId", "revision"); private static readonly HashSet WrongAddressAssignmentFields = Set("missionKey", "attempt", "mode", "authorizationCorrelationId", "productId", "productName", "packagingId", "packagingName", "packageQuantity", "sourceDropGuid", "sourceDropName", "sourceDropDescription", "sourceDropX", "sourceDropY", "sourceDropZ", "handoffDropGuid", "handoffDropName", "handoffDropDescription", "handoffDropX", "handoffDropY", "handoffDropZ", "rewardMultiplier"); private static readonly HashSet WrongAddressProgressFields = Set("missionKey", "attempt", "staged", "custody"); private static readonly HashSet RoomWithNoNameAssignmentFields = Set("missionKey", "attempt", "mode", "authorizationCorrelationId", "productId", "productName", "packagingId", "packagingName", "packageQuantity", "sourceDropGuid", "sourceDropName", "sourceDropDescription", "sourceDropX", "sourceDropY", "sourceDropZ", "handoffDropGuid", "handoffDropName", "handoffDropDescription", "handoffDropX", "handoffDropY", "handoffDropZ", "holdRoomKey", "expectedClosetCount", "holdDurationGameMinutes", "rewardMultiplier"); private static readonly HashSet RoomWithNoNameProgressFields = Set("missionKey", "attempt", "staged", "custody", "stowed", "holdSatisfied", "stowedAtGameMinutes", "holdingClosetGuid", "missingSincePassGameMinutes"); private static readonly HashSet ShortNoticeAssignmentFields = Set("missionKey", "attempt", "mode", "authorizationCorrelationId", "productId", "productName", "packagingId", "packagingName", "requiredQuantity", "handoffDropGuid", "handoffDropName", "handoffDropDescription", "handoffDropX", "handoffDropY", "handoffDropZ", "deadlineGameMinutes", "rewardMultiplier", "valueConvention"); private static readonly HashSet ShortNoticeProgressFields = Set("missionKey", "attempt", "observedQuantity", "lastShortfallNoticed", "spreadNoticed"); private static readonly HashSet KeepTheLightsOffAssignmentFields = Set("missionKey", "attempt", "mode", "authorizationCorrelationId", "expectedOwnedPropertyCount", "windowDurationGameMinutes", "deadlineGameMinutes"); private static readonly HashSet KeepTheLightsOffProgressFields = Set("missionKey", "attempt", "clearConfirmedAtGameMinutes", "breachSincePassGameMinutes"); private static readonly HashSet TheEnvelopeAssignmentFields = Set("missionKey", "attempt", "mode", "authorizationCorrelationId", "amountWholeDollars", "holdRoomKey", "expectedClosetCount", "deadlineGameMinutes"); private static readonly HashSet TheEnvelopeProgressFields = Set("missionKey", "attempt", "observedBalance", "lastShortfallNoticed", "spreadNoticed"); private static readonly HashSet ChiefRecordFields = Set("adopted", "adoptedTier", "adoptedKnownOffender", "demandRound", "state", "watchLinesSent", "lockdownAnnouncements", "lockdownEngaged", "paidLifts", "cooledLifts", "declineRepliesSent", "paymentBlockedNotices", "lastShortfallNoticed", "acceptedLogicalCorrelations", "nativeEffectIds", "revision"); public static bool TrySerialize(Release1StorySaveEnvelope? envelope, out string json, out Release1StoryCodecResult result) { json = string.Empty; if ((object)envelope == null || envelope.SchemaVersion != 11) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.UnsupportedSchema, "Release 1 story envelope schema was unsupported."); return false; } try { envelope.Story?.Validate(); EnvelopeDto value = new EnvelopeDto { SchemaVersion = 11, Story = (((object)envelope.Story == null) ? null : ToDto(envelope.Story)) }; json = JsonSerializer.Serialize(value, Options); result = Release1StoryCodecResult.Success(); return true; } catch (ArgumentException ex) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.InvalidStory, ex.Message); return false; } catch (Exception ex2) when (((ex2 is JsonException || ex2 is NotSupportedException || ex2 is InvalidOperationException) ? 1 : 0) != 0) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.SerializationFailed, ex2.Message); return false; } } public static bool TrySerialize(Release1StoryState? story, out string json, out Release1StoryCodecResult result) { return TrySerialize(new Release1StorySaveEnvelope(11, story), out json, out result); } public static bool TryDeserialize(string? json, out Release1StorySaveEnvelope? envelope, out Release1StoryCodecResult result) { envelope = null; if (string.IsNullOrWhiteSpace(json)) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.EmptyJson, "Story sidecar JSON was empty."); return false; } try { using JsonDocument jsonDocument = JsonDocument.Parse(json); if (jsonDocument.RootElement.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidRoot, "Story sidecar root was not an object."); } if (!ValidateFields(jsonDocument.RootElement, EnvelopeFields, out result)) { return false; } if (!RequireFields(jsonDocument.RootElement, EnvelopeFields, out result)) { return false; } if (!jsonDocument.RootElement.TryGetProperty("schemaVersion", out var value) || value.ValueKind != JsonValueKind.Number || !value.TryGetInt32(out var value2)) { return Fail(out result, Release1StoryCodecFailureReason.InvalidSchemaVersion, "schemaVersion was invalid."); } if (!SupportedReadSchemaVersions.Contains(value2)) { return Fail(out result, Release1StoryCodecFailureReason.UnsupportedSchema, "Story sidecar schema was unsupported."); } EnvelopeDto envelopeDto = JsonSerializer.Deserialize(json, Options); if (envelopeDto == null || envelopeDto.SchemaVersion != value2) { return Fail(out result, Release1StoryCodecFailureReason.InvalidStory, "Story envelope was invalid."); } if (envelopeDto.Story == null) { envelope = Release1StorySaveEnvelope.CreateEmpty(); result = Release1StoryCodecResult.Success(); return true; } if (!ValidateDtoFields(jsonDocument.RootElement.GetProperty("story"), StoryFields, out result)) { return false; } JsonElement value3 = jsonDocument.RootElement; JsonElement property = value3.GetProperty("story"); if (!RequireFields(property, RequiredStoryFields, out result)) { return false; } if (value2 >= 3 && !property.TryGetProperty("smallCourtesyAssignments", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: smallCourtesyAssignments."); } if (value2 >= 4 && !property.TryGetProperty("presentationReceipts", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: presentationReceipts."); } if (value2 >= 5 && !property.TryGetProperty("wrongAddressAssignments", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: wrongAddressAssignments."); } if (value2 >= 5 && !property.TryGetProperty("wrongAddressProgress", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: wrongAddressProgress."); } if (value2 >= 6 && !property.TryGetProperty("roomWithNoNameAssignments", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: roomWithNoNameAssignments."); } if (value2 >= 6 && !property.TryGetProperty("roomWithNoNameProgress", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: roomWithNoNameProgress."); } if (value2 >= 7 && !property.TryGetProperty("shortNoticeAssignments", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: shortNoticeAssignments."); } if (value2 >= 7 && !property.TryGetProperty("shortNoticeProgress", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: shortNoticeProgress."); } if (value2 >= 8 && !property.TryGetProperty("keepTheLightsOffAssignments", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: keepTheLightsOffAssignments."); } if (value2 >= 8 && !property.TryGetProperty("keepTheLightsOffProgress", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: keepTheLightsOffProgress."); } if (value2 >= 9 && !property.TryGetProperty("theEnvelopeAssignments", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: theEnvelopeAssignments."); } if (value2 >= 9 && !property.TryGetProperty("theEnvelopeProgress", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: theEnvelopeProgress."); } if (value2 >= 11 && !property.TryGetProperty("chiefRecord", out value3)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: chiefRecord."); } if (!ValidateArrays(property, value2, out result)) { return false; } Release1StoryState release1StoryState = FromDto(envelopeDto.Story, value2); release1StoryState.Validate(); envelope = new Release1StorySaveEnvelope(11, release1StoryState); result = Release1StoryCodecResult.Success(); return true; } catch (JsonException ex) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.MalformedJson, ex.Message); return false; } catch (ArgumentException ex2) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.InvalidStory, ex2.Message); return false; } catch (InvalidOperationException ex3) { result = Release1StoryCodecResult.Failure(Release1StoryCodecFailureReason.InvalidJsonType, ex3.Message); return false; } } private static bool ValidateArrays(JsonElement story, int version, out Release1StoryCodecResult result) { string[] array = new string[4] { "introLogicalCorrelationIds", "recognitionLogicalCorrelationIds", "missions", "nativeEffects" }; foreach (string text in array) { if (!story.TryGetProperty(text, out var value) || value.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, text + " was not an array."); } } foreach (JsonElement item in story.GetProperty("missions").EnumerateArray()) { if (item.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Mission was not an object."); } if (!ValidateDtoFields(item, MissionFields, out result)) { return false; } if (!RequireFields(item, MissionFields, out result)) { return false; } } foreach (JsonElement item2 in story.GetProperty("nativeEffects").EnumerateArray()) { if (item2.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Native effect was not an object."); } if (!ValidateDtoFields(item2, EffectFields, out result)) { return false; } if (!RequireFields(item2, EffectFields, out result)) { return false; } } if (story.TryGetProperty("phonePresentationAttempts", out var value2)) { if (value2.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "phonePresentationAttempts was not an array."); } foreach (JsonElement item3 in value2.EnumerateArray()) { if (item3.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Phone presentation attempt was not an object."); } if (!ValidateDtoFields(item3, PhonePresentationAttemptFields, out result)) { return false; } if (!RequireFields(item3, PhonePresentationAttemptFields, out result)) { return false; } } } if (story.TryGetProperty("smallCourtesyAssignments", out var value3)) { if (value3.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "smallCourtesyAssignments was not an array."); } foreach (JsonElement item4 in value3.EnumerateArray()) { if (item4.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Small Courtesy assignment was not an object."); } if (!ValidateDtoFields(item4, SmallCourtesyAssignmentFields, out result)) { return false; } if (!RequireFields(item4, SmallCourtesyAssignmentFields, out result)) { return false; } } } if (story.TryGetProperty("presentationReceipts", out var value4)) { if (value4.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "presentationReceipts was not an array."); } foreach (JsonElement item5 in value4.EnumerateArray()) { if (item5.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Presentation receipt was not an object."); } if (!ValidateDtoFields(item5, PresentationReceiptFields, out result)) { return false; } if (!RequireFields(item5, PresentationReceiptFields, out result)) { return false; } } } if (story.TryGetProperty("wrongAddressAssignments", out var value5)) { if (value5.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "wrongAddressAssignments was not an array."); } foreach (JsonElement item6 in value5.EnumerateArray()) { if (item6.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Wrong Address assignment was not an object."); } if (!ValidateDtoFields(item6, WrongAddressAssignmentFields, out result)) { return false; } if (!RequireFields(item6, WrongAddressAssignmentFields, out result)) { return false; } } } if (story.TryGetProperty("wrongAddressProgress", out var value6)) { if (value6.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "wrongAddressProgress was not an array."); } foreach (JsonElement item7 in value6.EnumerateArray()) { if (item7.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Wrong Address progress was not an object."); } if (!ValidateDtoFields(item7, WrongAddressProgressFields, out result)) { return false; } if (!RequireFields(item7, WrongAddressProgressFields, out result)) { return false; } } } if (story.TryGetProperty("roomWithNoNameAssignments", out var value7)) { if (value7.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "roomWithNoNameAssignments was not an array."); } foreach (JsonElement item8 in value7.EnumerateArray()) { if (item8.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Room With No Name assignment was not an object."); } if (!ValidateDtoFields(item8, RoomWithNoNameAssignmentFields, out result)) { return false; } if (!RequireFields(item8, RoomWithNoNameAssignmentFields, out result)) { return false; } } } if (story.TryGetProperty("roomWithNoNameProgress", out var value8)) { if (value8.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "roomWithNoNameProgress was not an array."); } foreach (JsonElement item9 in value8.EnumerateArray()) { if (item9.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Room With No Name progress was not an object."); } if (!ValidateDtoFields(item9, RoomWithNoNameProgressFields, out result)) { return false; } if (!RequireFields(item9, RoomWithNoNameProgressFields, out result)) { return false; } } } if (story.TryGetProperty("shortNoticeAssignments", out var value9)) { if (value9.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "shortNoticeAssignments was not an array."); } foreach (JsonElement item10 in value9.EnumerateArray()) { if (item10.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Short Notice assignment was not an object."); } if (!ValidateDtoFields(item10, ShortNoticeAssignmentFields, out result)) { return false; } if (!RequireFields(item10, ShortNoticeAssignmentFields, out result)) { return false; } } } if (story.TryGetProperty("shortNoticeProgress", out var value10)) { if (value10.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "shortNoticeProgress was not an array."); } foreach (JsonElement item11 in value10.EnumerateArray()) { if (item11.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Short Notice progress was not an object."); } if (!ValidateDtoFields(item11, ShortNoticeProgressFields, out result)) { return false; } if (!RequireFields(item11, ShortNoticeProgressFields, out result)) { return false; } } } if (story.TryGetProperty("keepTheLightsOffAssignments", out var value11)) { if (value11.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "keepTheLightsOffAssignments was not an array."); } foreach (JsonElement item12 in value11.EnumerateArray()) { if (item12.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Keep the Lights Off assignment was not an object."); } if (version >= 10) { if (!ValidateDtoFields(item12, KeepTheLightsOffAssignmentFields, out result)) { return false; } if (!RequireFields(item12, KeepTheLightsOffAssignmentFields, out result)) { return false; } } } } if (story.TryGetProperty("keepTheLightsOffProgress", out var value12)) { if (value12.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "keepTheLightsOffProgress was not an array."); } foreach (JsonElement item13 in value12.EnumerateArray()) { if (item13.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "Keep the Lights Off progress was not an object."); } if (!ValidateDtoFields(item13, KeepTheLightsOffProgressFields, out result)) { return false; } if (!RequireFields(item13, KeepTheLightsOffProgressFields, out result)) { return false; } } } if (story.TryGetProperty("theEnvelopeAssignments", out var value13)) { if (value13.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "theEnvelopeAssignments was not an array."); } foreach (JsonElement item14 in value13.EnumerateArray()) { if (item14.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "The Envelope assignment was not an object."); } if (!ValidateDtoFields(item14, TheEnvelopeAssignmentFields, out result)) { return false; } if (!RequireFields(item14, TheEnvelopeAssignmentFields, out result)) { return false; } } } if (story.TryGetProperty("theEnvelopeProgress", out var value14)) { if (value14.ValueKind != JsonValueKind.Array) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "theEnvelopeProgress was not an array."); } foreach (JsonElement item15 in value14.EnumerateArray()) { if (item15.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "The Envelope progress was not an object."); } if (!ValidateDtoFields(item15, TheEnvelopeProgressFields, out result)) { return false; } if (!RequireFields(item15, TheEnvelopeProgressFields, out result)) { return false; } } } if (version >= 11 && story.TryGetProperty("chiefRecord", out var value15) && value15.ValueKind != JsonValueKind.Null) { if (value15.ValueKind != JsonValueKind.Object) { return Fail(out result, Release1StoryCodecFailureReason.InvalidJsonType, "chiefRecord was not an object."); } if (!ValidateDtoFields(value15, ChiefRecordFields, out result)) { return false; } if (!RequireFields(value15, ChiefRecordFields, out result)) { return false; } } result = Release1StoryCodecResult.Success(); return true; } private static bool ValidateDtoFields(JsonElement element, HashSet fields, out Release1StoryCodecResult result) { return ValidateFields(element, fields, out result); } private static bool ValidateFields(JsonElement element, HashSet fields, out Release1StoryCodecResult result) { HashSet hashSet = new HashSet(StringComparer.Ordinal); foreach (JsonProperty item in element.EnumerateObject()) { if (!fields.Contains(item.Name) || !hashSet.Add(item.Name)) { return Fail(out result, Release1StoryCodecFailureReason.UnknownField, "Unknown or duplicate field: " + item.Name + "."); } } result = Release1StoryCodecResult.Success(); return true; } private static bool RequireFields(JsonElement element, HashSet fields, out Release1StoryCodecResult result) { foreach (string field in fields) { if (!element.TryGetProperty(field, out var _)) { return Fail(out result, Release1StoryCodecFailureReason.MissingRequiredField, "Required field was missing: " + field + "."); } } result = Release1StoryCodecResult.Success(); return true; } private static bool Fail(out Release1StoryCodecResult result, Release1StoryCodecFailureReason reason, string message) { result = Release1StoryCodecResult.Failure(reason, message); return false; } private static HashSet Set(params string[] values) { return new HashSet(values, StringComparer.Ordinal); } private static StoryDto ToDto(Release1StoryState story) { return new StoryDto { PlayerId = story.PlayerId, Standing = story.Standing, RelationshipState = story.RelationshipState, Release1Recognized = story.Release1Recognized, IntroLogicalCorrelationIds = story.IntroLogicalCorrelationIds.ToArray(), RecognitionLogicalCorrelationIds = story.RecognitionLogicalCorrelationIds.ToArray(), Missions = story.Missions.Select(ToDto).ToArray(), NativeEffects = story.NativeEffects.OrderBy((Release1NativeEffectJournalEntry e) => e.EffectId, StringComparer.Ordinal).Select(ToDto).ToArray(), PhonePresentationAttempts = story.PhonePresentationAttempts.Select(ToDto).ToArray(), SmallCourtesyAssignments = story.SmallCourtesyAssignments.OrderBy((Release1SmallCourtesyAssignment assignment) => assignment.Attempt).Select(ToDto).ToArray(), PresentationReceipts = story.PresentationReceipts.Select(ToDto).ToArray(), WrongAddressAssignments = story.WrongAddressAssignments.OrderBy((Release1WrongAddressAssignment assignment) => assignment.Attempt).Select(ToDto).ToArray(), WrongAddressProgress = story.WrongAddressProgress.OrderBy((Release1WrongAddressProgress progress) => progress.Attempt).Select(ToDto).ToArray(), RoomWithNoNameAssignments = story.RoomWithNoNameAssignments.OrderBy((Release1RoomWithNoNameAssignment assignment) => assignment.Attempt).Select(ToDto).ToArray(), RoomWithNoNameProgress = story.RoomWithNoNameProgress.OrderBy((Release1RoomWithNoNameProgress progress) => progress.Attempt).Select(ToDto).ToArray(), ShortNoticeAssignments = story.ShortNoticeAssignments.OrderBy((Release1ShortNoticeAssignment assignment) => assignment.Attempt).Select(ToDto).ToArray(), ShortNoticeProgress = story.ShortNoticeProgress.OrderBy((Release1ShortNoticeProgress progress) => progress.Attempt).Select(ToDto).ToArray(), KeepTheLightsOffAssignments = story.KeepTheLightsOffAssignments.OrderBy((Release1KeepTheLightsOffAssignment assignment) => assignment.Attempt).Select(ToDto).ToArray(), KeepTheLightsOffProgress = story.KeepTheLightsOffProgress.OrderBy((Release1KeepTheLightsOffProgress progress) => progress.Attempt).Select(ToDto).ToArray(), TheEnvelopeAssignments = story.TheEnvelopeAssignments.OrderBy((Release1TheEnvelopeAssignment assignment) => assignment.Attempt).Select(ToDto).ToArray(), TheEnvelopeProgress = story.TheEnvelopeProgress.OrderBy((Release1TheEnvelopeProgress progress) => progress.Attempt).Select(ToDto).ToArray(), ChiefRecord = (((object)story.ChiefRecord == null) ? null : ToDto(story.ChiefRecord)), Revision = story.Revision }; } private static MissionDto ToDto(Release1MissionRecord m) { return new MissionDto { MissionKey = m.MissionKey, State = m.State, Attempt = m.Attempt, TermsVersion = m.TermsVersion, AcceptedGameTimeHours = m.AcceptedGameTimeHours, DeadlineGameTimeHours = m.DeadlineGameTimeHours, LastOutcome = m.LastOutcome, StandingPenaltyApplied = m.StandingPenaltyApplied, PenaltyReceiptIds = m.PenaltyReceiptIds.ToArray(), MakeGoodFailures = m.MakeGoodFailures, RecoveryMode = m.RecoveryMode, RewardAuthorizationReceiptId = m.RewardAuthorizationReceiptId, QuietConditionAwarded = m.QuietConditionAwarded, NativeEffectIds = m.NativeEffectIds.ToArray(), PresentationCorrelationIds = m.PresentationCorrelationIds.ToArray(), AcceptedLogicalCorrelations = m.AcceptedLogicalCorrelations.ToArray(), NativePresentationRef = m.NativePresentationRef, Revision = m.Revision }; } private static EffectDto ToDto(Release1NativeEffectJournalEntry e) { return new EffectDto { EffectId = e.EffectId, MissionKey = e.MissionKey, Attempt = e.Attempt, EffectKind = e.EffectKind, SourceIdentity = e.SourceIdentity, DestinationIdentity = e.DestinationIdentity, AmountOrCargoIdentity = e.AmountOrCargoIdentity, Phase = e.Phase, NativeReceiptId = e.NativeReceiptId, PreparedStoryRevision = e.PreparedStoryRevision, CommittedStoryCorrelationId = e.CommittedStoryCorrelationId, AuthorizedStoryCorrelationId = e.AuthorizedStoryCorrelationId, ExecutionBlocked = e.ExecutionBlocked, AuthorizedMissionRevision = e.AuthorizedMissionRevision }; } private static Release1PhonePresentationAttemptDto ToDto(Release1PhonePresentationAttempt attempt) { return new Release1PhonePresentationAttemptDto { CorrelationId = attempt.CorrelationId, MissionKey = attempt.MissionKey, Attempt = attempt.Attempt, Role = attempt.Role, RequiredPriorCorrelationId = attempt.RequiredPriorCorrelationId, State = attempt.State, Revision = attempt.Revision }; } private static SmallCourtesyAssignmentDto ToDto(Release1SmallCourtesyAssignment assignment) { return new SmallCourtesyAssignmentDto { MissionKey = assignment.MissionKey, Attempt = assignment.Attempt, Mode = assignment.Mode, AuthorizationCorrelationId = assignment.AuthorizationCorrelationId, ProductId = assignment.ProductId, ProductName = assignment.ProductName, SelectionAskingPrice = assignment.SelectionAskingPrice, PackagingId = assignment.PackagingId, PackagingName = assignment.PackagingName, DeadDropGuid = assignment.DeadDropGuid, DeadDropName = assignment.DeadDropName, DeadDropDescription = assignment.DeadDropDescription, DeadDropX = assignment.DeadDropX, DeadDropY = assignment.DeadDropY, DeadDropZ = assignment.DeadDropZ, RewardMultiplier = assignment.RewardMultiplier }; } private static PresentationReceiptDto ToDto(Release1PresentationReceipt receipt) { return new PresentationReceiptDto { CorrelationId = receipt.CorrelationId, Revision = receipt.Revision }; } private static WrongAddressAssignmentDto ToDto(Release1WrongAddressAssignment assignment) { return new WrongAddressAssignmentDto { MissionKey = assignment.MissionKey, Attempt = assignment.Attempt, Mode = assignment.Mode, AuthorizationCorrelationId = assignment.AuthorizationCorrelationId, ProductId = assignment.ProductId, ProductName = assignment.ProductName, PackagingId = assignment.PackagingId, PackagingName = assignment.PackagingName, PackageQuantity = assignment.PackageQuantity, SourceDropGuid = assignment.SourceDropGuid, SourceDropName = assignment.SourceDropName, SourceDropDescription = assignment.SourceDropDescription, SourceDropX = assignment.SourceDropX, SourceDropY = assignment.SourceDropY, SourceDropZ = assignment.SourceDropZ, HandoffDropGuid = assignment.HandoffDropGuid, HandoffDropName = assignment.HandoffDropName, HandoffDropDescription = assignment.HandoffDropDescription, HandoffDropX = assignment.HandoffDropX, HandoffDropY = assignment.HandoffDropY, HandoffDropZ = assignment.HandoffDropZ, RewardMultiplier = assignment.RewardMultiplier }; } private static WrongAddressProgressDto ToDto(Release1WrongAddressProgress progress) { return new WrongAddressProgressDto { MissionKey = progress.MissionKey, Attempt = progress.Attempt, Staged = progress.Staged, Custody = progress.Custody }; } private static RoomWithNoNameAssignmentDto ToDto(Release1RoomWithNoNameAssignment assignment) { return new RoomWithNoNameAssignmentDto { MissionKey = assignment.MissionKey, Attempt = assignment.Attempt, Mode = assignment.Mode, AuthorizationCorrelationId = assignment.AuthorizationCorrelationId, ProductId = assignment.ProductId, ProductName = assignment.ProductName, PackagingId = assignment.PackagingId, PackagingName = assignment.PackagingName, PackageQuantity = assignment.PackageQuantity, SourceDropGuid = assignment.SourceDropGuid, SourceDropName = assignment.SourceDropName, SourceDropDescription = assignment.SourceDropDescription, SourceDropX = assignment.SourceDropX, SourceDropY = assignment.SourceDropY, SourceDropZ = assignment.SourceDropZ, HandoffDropGuid = assignment.HandoffDropGuid, HandoffDropName = assignment.HandoffDropName, HandoffDropDescription = assignment.HandoffDropDescription, HandoffDropX = assignment.HandoffDropX, HandoffDropY = assignment.HandoffDropY, HandoffDropZ = assignment.HandoffDropZ, HoldRoomKey = assignment.HoldRoomKey, ExpectedClosetCount = assignment.ExpectedClosetCount, HoldDurationGameMinutes = assignment.HoldDurationGameMinutes, RewardMultiplier = assignment.RewardMultiplier }; } private static RoomWithNoNameProgressDto ToDto(Release1RoomWithNoNameProgress progress) { return new RoomWithNoNameProgressDto { MissionKey = progress.MissionKey, Attempt = progress.Attempt, Staged = progress.Staged, Custody = progress.Custody, Stowed = progress.Stowed, HoldSatisfied = progress.HoldSatisfied, StowedAtGameMinutes = progress.StowedAtGameMinutes, HoldingClosetGuid = progress.HoldingClosetGuid, MissingSincePassGameMinutes = progress.MissingSincePassGameMinutes }; } private static ShortNoticeAssignmentDto ToDto(Release1ShortNoticeAssignment assignment) { return new ShortNoticeAssignmentDto { MissionKey = assignment.MissionKey, Attempt = assignment.Attempt, Mode = assignment.Mode, AuthorizationCorrelationId = assignment.AuthorizationCorrelationId, ProductId = assignment.ProductId, ProductName = assignment.ProductName, PackagingId = assignment.PackagingId, PackagingName = assignment.PackagingName, RequiredQuantity = assignment.RequiredQuantity, HandoffDropGuid = assignment.HandoffDropGuid, HandoffDropName = assignment.HandoffDropName, HandoffDropDescription = assignment.HandoffDropDescription, HandoffDropX = assignment.HandoffDropX, HandoffDropY = assignment.HandoffDropY, HandoffDropZ = assignment.HandoffDropZ, DeadlineGameMinutes = assignment.DeadlineGameMinutes, RewardMultiplier = assignment.RewardMultiplier, ValueConvention = assignment.ValueConvention }; } private static ShortNoticeProgressDto ToDto(Release1ShortNoticeProgress progress) { return new ShortNoticeProgressDto { MissionKey = progress.MissionKey, Attempt = progress.Attempt, ObservedQuantity = progress.ObservedQuantity, LastShortfallNoticed = progress.LastShortfallNoticed, SpreadNoticed = progress.SpreadNoticed }; } private static KeepTheLightsOffAssignmentDto ToDto(Release1KeepTheLightsOffAssignment assignment) { return new KeepTheLightsOffAssignmentDto { MissionKey = assignment.MissionKey, Attempt = assignment.Attempt, Mode = assignment.Mode, AuthorizationCorrelationId = assignment.AuthorizationCorrelationId, ExpectedOwnedPropertyCount = assignment.ExpectedOwnedPropertyCount, WindowDurationGameMinutes = assignment.WindowDurationGameMinutes, DeadlineGameMinutes = assignment.DeadlineGameMinutes }; } private static KeepTheLightsOffProgressDto ToDto(Release1KeepTheLightsOffProgress progress) { return new KeepTheLightsOffProgressDto { MissionKey = progress.MissionKey, Attempt = progress.Attempt, ClearConfirmedAtGameMinutes = progress.ClearConfirmedAtGameMinutes, BreachSincePassGameMinutes = progress.BreachSincePassGameMinutes }; } private static TheEnvelopeAssignmentDto ToDto(Release1TheEnvelopeAssignment assignment) { return new TheEnvelopeAssignmentDto { MissionKey = assignment.MissionKey, Attempt = assignment.Attempt, Mode = assignment.Mode, AuthorizationCorrelationId = assignment.AuthorizationCorrelationId, AmountWholeDollars = assignment.AmountWholeDollars, HoldRoomKey = assignment.HoldRoomKey, ExpectedClosetCount = assignment.ExpectedClosetCount, DeadlineGameMinutes = assignment.DeadlineGameMinutes }; } private static TheEnvelopeProgressDto ToDto(Release1TheEnvelopeProgress progress) { return new TheEnvelopeProgressDto { MissionKey = progress.MissionKey, Attempt = progress.Attempt, ObservedBalance = progress.ObservedBalance, LastShortfallNoticed = progress.LastShortfallNoticed, SpreadNoticed = progress.SpreadNoticed }; } private static ChiefRecordDto ToDto(Release1ChiefRecord record) { return new ChiefRecordDto { Adopted = record.Adopted, AdoptedTier = record.AdoptedTier, AdoptedKnownOffender = record.AdoptedKnownOffender, DemandRound = record.DemandRound, State = record.State, WatchLinesSent = record.WatchLinesSent, LockdownAnnouncements = record.LockdownAnnouncements, LockdownEngaged = record.LockdownEngaged, PaidLifts = record.PaidLifts, CooledLifts = record.CooledLifts, DeclineRepliesSent = record.DeclineRepliesSent, PaymentBlockedNotices = record.PaymentBlockedNotices, LastShortfallNoticed = record.LastShortfallNoticed, AcceptedLogicalCorrelations = record.AcceptedLogicalCorrelations.ToArray(), NativeEffectIds = record.NativeEffectIds.ToArray(), Revision = record.Revision }; } private static Release1StoryState FromDto(StoryDto dto, int version) { return new Release1StoryState(dto.PlayerId, dto.Standing, dto.RelationshipState, dto.Release1Recognized, dto.IntroLogicalCorrelationIds, dto.RecognitionLogicalCorrelationIds, dto.Missions.Select(FromDto).ToArray(), dto.NativeEffects.Select(FromDto).ToArray(), dto.Revision, (version >= 11 && dto.ChiefRecord != null) ? FromDto(dto.ChiefRecord) : null) { PhonePresentationAttempts = (dto.PhonePresentationAttempts?.Select(FromDto).ToArray() ?? Array.Empty()), SmallCourtesyAssignments = (dto.SmallCourtesyAssignments?.Select(FromDto).ToArray() ?? Array.Empty()), PresentationReceipts = (dto.PresentationReceipts?.Select(FromDto).ToArray() ?? Array.Empty()), WrongAddressAssignments = (dto.WrongAddressAssignments?.Select(FromDto).ToArray() ?? Array.Empty()), WrongAddressProgress = (dto.WrongAddressProgress?.Select(FromDto).ToArray() ?? Array.Empty()), RoomWithNoNameAssignments = (dto.RoomWithNoNameAssignments?.Select(FromDto).ToArray() ?? Array.Empty()), RoomWithNoNameProgress = (dto.RoomWithNoNameProgress?.Select(FromDto).ToArray() ?? Array.Empty()), ShortNoticeAssignments = (dto.ShortNoticeAssignments?.Select(FromDto).ToArray() ?? Array.Empty()), ShortNoticeProgress = (dto.ShortNoticeProgress?.Select(FromDto).ToArray() ?? Array.Empty()), KeepTheLightsOffAssignments = ((version < 10) ? Array.Empty() : (dto.KeepTheLightsOffAssignments?.Select(FromDto).ToArray() ?? Array.Empty())), KeepTheLightsOffProgress = ((version < 10) ? Array.Empty() : (dto.KeepTheLightsOffProgress?.Select(FromDto).ToArray() ?? Array.Empty())), TheEnvelopeAssignments = (dto.TheEnvelopeAssignments?.Select(FromDto).ToArray() ?? Array.Empty()), TheEnvelopeProgress = (dto.TheEnvelopeProgress?.Select(FromDto).ToArray() ?? Array.Empty()) }; } private static Release1MissionRecord FromDto(MissionDto dto) { return new Release1MissionRecord(dto.MissionKey, dto.State, dto.Attempt, dto.TermsVersion, dto.AcceptedGameTimeHours, dto.DeadlineGameTimeHours, dto.LastOutcome, dto.StandingPenaltyApplied, dto.PenaltyReceiptIds, dto.MakeGoodFailures, dto.RecoveryMode, dto.RewardAuthorizationReceiptId, dto.QuietConditionAwarded, dto.NativeEffectIds, dto.PresentationCorrelationIds, dto.AcceptedLogicalCorrelations, dto.NativePresentationRef, dto.Revision); } private static Release1NativeEffectJournalEntry FromDto(EffectDto dto) { return new Release1NativeEffectJournalEntry(dto.EffectId, dto.MissionKey, dto.Attempt, dto.EffectKind, dto.SourceIdentity, dto.DestinationIdentity, dto.AmountOrCargoIdentity, dto.Phase, dto.NativeReceiptId, dto.PreparedStoryRevision, dto.CommittedStoryCorrelationId, dto.AuthorizedStoryCorrelationId, dto.ExecutionBlocked, dto.AuthorizedMissionRevision); } private static Release1PhonePresentationAttempt FromDto(Release1PhonePresentationAttemptDto dto) { return new Release1PhonePresentationAttempt(dto.CorrelationId, dto.MissionKey, dto.Attempt, dto.Role, dto.RequiredPriorCorrelationId, dto.State, dto.Revision); } private static Release1SmallCourtesyAssignment FromDto(SmallCourtesyAssignmentDto dto) { return new Release1SmallCourtesyAssignment(dto.MissionKey, dto.Attempt, dto.Mode, dto.AuthorizationCorrelationId, dto.ProductId, dto.ProductName, dto.SelectionAskingPrice, dto.PackagingId, dto.PackagingName, dto.DeadDropGuid, dto.DeadDropName, dto.DeadDropDescription, dto.DeadDropX, dto.DeadDropY, dto.DeadDropZ, dto.RewardMultiplier); } private static Release1PresentationReceipt FromDto(PresentationReceiptDto dto) { return new Release1PresentationReceipt(dto.CorrelationId, dto.Revision); } private static Release1WrongAddressAssignment FromDto(WrongAddressAssignmentDto dto) { return new Release1WrongAddressAssignment(dto.MissionKey, dto.Attempt, dto.Mode, dto.AuthorizationCorrelationId, dto.ProductId, dto.ProductName, dto.PackagingId, dto.PackagingName, dto.PackageQuantity, dto.SourceDropGuid, dto.SourceDropName, dto.SourceDropDescription, dto.SourceDropX, dto.SourceDropY, dto.SourceDropZ, dto.HandoffDropGuid, dto.HandoffDropName, dto.HandoffDropDescription, dto.HandoffDropX, dto.HandoffDropY, dto.HandoffDropZ, dto.RewardMultiplier); } private static Release1WrongAddressProgress FromDto(WrongAddressProgressDto dto) { return new Release1WrongAddressProgress(dto.MissionKey, dto.Attempt, dto.Staged, dto.Custody); } private static Release1RoomWithNoNameAssignment FromDto(RoomWithNoNameAssignmentDto dto) { return new Release1RoomWithNoNameAssignment(dto.MissionKey, dto.Attempt, dto.Mode, dto.AuthorizationCorrelationId, dto.ProductId, dto.ProductName, dto.PackagingId, dto.PackagingName, dto.PackageQuantity, dto.SourceDropGuid, dto.SourceDropName, dto.SourceDropDescription, dto.SourceDropX, dto.SourceDropY, dto.SourceDropZ, dto.HandoffDropGuid, dto.HandoffDropName, dto.HandoffDropDescription, dto.HandoffDropX, dto.HandoffDropY, dto.HandoffDropZ, dto.HoldRoomKey, dto.ExpectedClosetCount, dto.HoldDurationGameMinutes, dto.RewardMultiplier); } private static Release1RoomWithNoNameProgress FromDto(RoomWithNoNameProgressDto dto) { return new Release1RoomWithNoNameProgress(dto.MissionKey, dto.Attempt, dto.Staged, dto.Custody, dto.Stowed, dto.HoldSatisfied, dto.StowedAtGameMinutes, dto.HoldingClosetGuid, dto.MissingSincePassGameMinutes); } private static Release1ShortNoticeAssignment FromDto(ShortNoticeAssignmentDto dto) { return new Release1ShortNoticeAssignment(dto.MissionKey, dto.Attempt, dto.Mode, dto.AuthorizationCorrelationId, dto.ProductId, dto.ProductName, dto.PackagingId, dto.PackagingName, dto.RequiredQuantity, dto.HandoffDropGuid, dto.HandoffDropName, dto.HandoffDropDescription, dto.HandoffDropX, dto.HandoffDropY, dto.HandoffDropZ, dto.DeadlineGameMinutes, dto.RewardMultiplier, dto.ValueConvention); } private static Release1ShortNoticeProgress FromDto(ShortNoticeProgressDto dto) { return new Release1ShortNoticeProgress(dto.MissionKey, dto.Attempt, dto.ObservedQuantity, dto.LastShortfallNoticed, dto.SpreadNoticed); } private static Release1KeepTheLightsOffAssignment FromDto(KeepTheLightsOffAssignmentDto dto) { return new Release1KeepTheLightsOffAssignment(dto.MissionKey, dto.Attempt, dto.Mode, dto.AuthorizationCorrelationId, dto.ExpectedOwnedPropertyCount, dto.WindowDurationGameMinutes, dto.DeadlineGameMinutes); } private static Release1KeepTheLightsOffProgress FromDto(KeepTheLightsOffProgressDto dto) { return new Release1KeepTheLightsOffProgress(dto.MissionKey, dto.Attempt, dto.ClearConfirmedAtGameMinutes, dto.BreachSincePassGameMinutes); } private static Release1TheEnvelopeAssignment FromDto(TheEnvelopeAssignmentDto dto) { return new Release1TheEnvelopeAssignment(dto.MissionKey, dto.Attempt, dto.Mode, dto.AuthorizationCorrelationId, dto.AmountWholeDollars, dto.HoldRoomKey, dto.ExpectedClosetCount, dto.DeadlineGameMinutes); } private static Release1TheEnvelopeProgress FromDto(TheEnvelopeProgressDto dto) { return new Release1TheEnvelopeProgress(dto.MissionKey, dto.Attempt, dto.ObservedBalance, dto.LastShortfallNoticed, dto.SpreadNoticed); } private static Release1ChiefRecord FromDto(ChiefRecordDto dto) { return new Release1ChiefRecord(dto.Adopted, dto.AdoptedTier, dto.AdoptedKnownOffender, dto.DemandRound, dto.State, dto.WatchLinesSent, dto.LockdownAnnouncements, dto.LockdownEngaged, dto.PaidLifts, dto.CooledLifts, dto.DeclineRepliesSent, dto.PaymentBlockedNotices, dto.LastShortfallNoticed, dto.AcceptedLogicalCorrelations ?? Array.Empty(), dto.NativeEffectIds ?? Array.Empty(), dto.Revision); } } public sealed record Release1StorySaveEnvelope { public int SchemaVersion { get; init; } public Release1StoryState? Story { get; init; } public Release1StorySaveEnvelope(int SchemaVersion, Release1StoryState? Story) { this.SchemaVersion = SchemaVersion; this.Story = Story; } public static Release1StorySaveEnvelope CreateEmpty() { return new Release1StorySaveEnvelope(11, null); } public bool Equals(Release1StorySaveEnvelope? other) { if ((object)other != null && SchemaVersion == other.SchemaVersion) { if ((object)Story != null || (object)other.Story != null) { if ((object)Story != null) { return Story.ValueEquals(other.Story); } return false; } return true; } return false; } public override int GetHashCode() { return HashCode.Combine(SchemaVersion, Story); } } public enum Release1StorySavePathFailureReason { None, ActiveSaveFolderMissing, InvalidActiveSaveFolder } public sealed record Release1StorySavePathResult(bool Succeeded, Release1StorySavePathFailureReason Reason, string Message) { public static Release1StorySavePathResult Success() { return new Release1StorySavePathResult(Succeeded: true, Release1StorySavePathFailureReason.None, string.Empty); } public static Release1StorySavePathResult Failure(Release1StorySavePathFailureReason reason, string message) { return new Release1StorySavePathResult(Succeeded: false, reason, message); } } public sealed class Release1StorySavePath { public string ActiveSaveFolder { get; } public string SidecarDirectory { get; } public string SidecarFilePath { get; } private Release1StorySavePath(string folder) { ActiveSaveFolder = folder; SidecarDirectory = Path.Combine(folder, "OrganizedCrime"); SidecarFilePath = Path.Combine(SidecarDirectory, "release1-story.json"); } public static bool TryCreate(string? folder, out Release1StorySavePath? path, out Release1StorySavePathResult result) { path = null; if (string.IsNullOrWhiteSpace(folder)) { result = Release1StorySavePathResult.Failure(Release1StorySavePathFailureReason.InvalidActiveSaveFolder, "Active save folder was empty."); return false; } try { string fullPath = Path.GetFullPath(folder.Trim()); if (!Directory.Exists(fullPath)) { result = Release1StorySavePathResult.Failure(Release1StorySavePathFailureReason.ActiveSaveFolderMissing, "Active save folder did not exist."); return false; } path = new Release1StorySavePath(fullPath); result = Release1StorySavePathResult.Success(); return true; } catch (Exception ex) when (((ex is ArgumentException || ex is IOException || ex is UnauthorizedAccessException || ex is NotSupportedException) ? 1 : 0) != 0) { result = Release1StorySavePathResult.Failure(Release1StorySavePathFailureReason.InvalidActiveSaveFolder, ex.Message); return false; } } } public enum Release1StoryStoreLoadStatus { Loaded, Empty, Failed } public enum Release1StoryStoreFailureReason { None, SidecarReadFailed, EmptyOrMalformedJson, InvalidStory, StaleRevision, RevisionConflict, SerializationFailed, DirectoryCreationFailed, TemporaryFileWriteFailed, AtomicReplacementFailed } public enum Release1StoryStoreUpdateStatus { Updated, Idempotent, Rejected } public sealed record Release1StoryStoreLoadResult(bool Succeeded, Release1StoryStoreLoadStatus Status, Release1StorySaveEnvelope? Envelope, Release1StoryStoreFailureReason FailureReason, string Message, string? RawContents = null); public sealed record Release1StoryStoreUpdateResult(bool Succeeded, Release1StoryStoreUpdateStatus Status, Release1StorySaveEnvelope? Envelope, Release1StoryStoreFailureReason FailureReason, string Message); public interface IRelease1StoryFileSystem { bool FileExists(string path); string ReadAllText(string path); void CreateDirectory(string path); string CreateTemporaryPath(string directory, string targetPath); void WriteAllTextAndFlush(string path, string contents); void ReplaceAtomically(string temporaryPath, string targetPath); void DeleteFile(string path); } public sealed class Release1StoryStateStore { private sealed class DefaultFileSystem : IRelease1StoryFileSystem { public bool FileExists(string path) { return File.Exists(path); } public string ReadAllText(string path) { return File.ReadAllText(path); } public void CreateDirectory(string path) { Directory.CreateDirectory(path); } public string CreateTemporaryPath(string directory, string targetPath) { return Path.Combine(directory, $".{Path.GetFileName(targetPath)}.{Guid.NewGuid():N}.tmp"); } public void WriteAllTextAndFlush(string path, string contents) { using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None); using StreamWriter streamWriter = new StreamWriter(fileStream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); streamWriter.Write(contents); streamWriter.Flush(); fileStream.Flush(flushToDisk: true); } public void ReplaceAtomically(string temporaryPath, string targetPath) { if (File.Exists(targetPath)) { File.Replace(temporaryPath, targetPath, null, ignoreMetadataErrors: true); } else { File.Move(temporaryPath, targetPath); } } public void DeleteFile(string path) { if (File.Exists(path)) { File.Delete(path); } } } private static readonly ConcurrentDictionary Locks = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); private readonly IRelease1StoryFileSystem _fileSystem; public Release1StorySavePath SavePath { get; } public Release1StoryStateStore(Release1StorySavePath savePath, IRelease1StoryFileSystem? fileSystem = null) { SavePath = savePath ?? throw new ArgumentNullException("savePath"); _fileSystem = fileSystem ?? new DefaultFileSystem(); } public bool TryLoad(out Release1StoryStoreLoadResult result) { lock (GetLock()) { try { result = LoadLocked(); } catch (Exception ex) { result = new Release1StoryStoreLoadResult(Succeeded: false, Release1StoryStoreLoadStatus.Failed, null, Release1StoryStoreFailureReason.SidecarReadFailed, ex.Message); } return result.Succeeded; } } public bool TryUpdate(Release1StoryState? story, out Release1StoryStoreUpdateResult result) { return TryUpdate(new Release1StorySaveEnvelope(11, story), out result); } public bool TryUpdate(Release1StorySaveEnvelope incoming, out Release1StoryStoreUpdateResult result) { lock (GetLock()) { if ((object)incoming == null) { return Reject(Release1StoryStoreFailureReason.InvalidStory, "Incoming story envelope was null.", out result); } if (!Release1StorySaveCodec.TrySerialize(incoming, out string json, out Release1StoryCodecResult result2)) { return Reject(Map(result2.Reason), result2.Message, out result); } Release1StoryStoreLoadResult release1StoryStoreLoadResult = LoadLocked(); if (!release1StoryStoreLoadResult.Succeeded) { return Reject(release1StoryStoreLoadResult.FailureReason, release1StoryStoreLoadResult.Message, out result); } Release1StorySaveEnvelope envelope = release1StoryStoreLoadResult.Envelope; long num = envelope.Story?.Revision ?? (-1); long num2 = incoming.Story?.Revision ?? (-1); if ((object)envelope.Story != null && (object)incoming.Story != null && incoming.Story.PlayerId != envelope.Story.PlayerId) { return Reject(Release1StoryStoreFailureReason.RevisionConflict, "Story player identity conflicted.", out result); } if (num2 < num) { return Reject(Release1StoryStoreFailureReason.StaleRevision, "Incoming story revision was stale.", out result); } if (num2 == num && envelope.Equals(incoming)) { result = new Release1StoryStoreUpdateResult(Succeeded: true, Release1StoryStoreUpdateStatus.Idempotent, envelope, Release1StoryStoreFailureReason.None, "Story update was already stored."); return true; } if (num2 == num) { return Reject(Release1StoryStoreFailureReason.RevisionConflict, "Incoming story revision conflicted with stored state.", out result); } if (!TryWriteAtomically(json, out (Release1StoryStoreFailureReason, string) failure)) { return Reject(failure.Item1, failure.Item2, out result); } result = new Release1StoryStoreUpdateResult(Succeeded: true, Release1StoryStoreUpdateStatus.Updated, incoming, Release1StoryStoreFailureReason.None, "Story update was stored."); return true; } } public bool TryPreserveRawContents(string rawContents, out Release1StoryStoreUpdateResult result) { lock (GetLock()) { if (!TryWriteAtomically(rawContents, out (Release1StoryStoreFailureReason, string) failure)) { return Reject(failure.Item1, failure.Item2, out result); } result = new Release1StoryStoreUpdateResult(Succeeded: true, Release1StoryStoreUpdateStatus.Updated, null, Release1StoryStoreFailureReason.None, "Quarantined story sidecar bytes were preserved."); return true; } } private Release1StoryStoreLoadResult LoadLocked() { try { if (!_fileSystem.FileExists(SavePath.SidecarFilePath)) { return new Release1StoryStoreLoadResult(Succeeded: true, Release1StoryStoreLoadStatus.Empty, Release1StorySaveEnvelope.CreateEmpty(), Release1StoryStoreFailureReason.None, "Story sidecar was absent; using empty envelope."); } string text = _fileSystem.ReadAllText(SavePath.SidecarFilePath); if (!Release1StorySaveCodec.TryDeserialize(text, out Release1StorySaveEnvelope envelope, out Release1StoryCodecResult result)) { return new Release1StoryStoreLoadResult(Succeeded: false, Release1StoryStoreLoadStatus.Failed, null, Map(result.Reason), result.Message, text); } return new Release1StoryStoreLoadResult(Succeeded: true, Release1StoryStoreLoadStatus.Loaded, envelope, Release1StoryStoreFailureReason.None, "Story sidecar was loaded."); } catch (Exception ex) { return new Release1StoryStoreLoadResult(Succeeded: false, Release1StoryStoreLoadStatus.Failed, null, Release1StoryStoreFailureReason.SidecarReadFailed, ex.Message); } } private bool TryWriteAtomically(string json, out (Release1StoryStoreFailureReason Reason, string Message) failure) { string text = null; Exception ex = null; try { try { _fileSystem.CreateDirectory(SavePath.SidecarDirectory); } catch (Exception ex2) { failure = (Reason: Release1StoryStoreFailureReason.DirectoryCreationFailed, Message: ex2.Message); return false; } try { text = _fileSystem.CreateTemporaryPath(SavePath.SidecarDirectory, SavePath.SidecarFilePath); } catch (Exception ex3) { ex = ex3; failure = (Reason: Release1StoryStoreFailureReason.TemporaryFileWriteFailed, Message: ex3.Message); return false; } try { _fileSystem.WriteAllTextAndFlush(text, json); } catch (Exception ex4) { ex = ex4; failure = (Reason: Release1StoryStoreFailureReason.TemporaryFileWriteFailed, Message: ex4.Message); return false; } try { _fileSystem.ReplaceAtomically(text, SavePath.SidecarFilePath); text = null; failure = (Reason: Release1StoryStoreFailureReason.None, Message: string.Empty); return true; } catch (Exception ex5) { ex = ex5; failure = (Reason: Release1StoryStoreFailureReason.AtomicReplacementFailed, Message: ex5.Message); return false; } } finally { if (text != null) { try { _fileSystem.DeleteFile(text); } catch when (ex != null) { } } } } private static object GetLockFor(string path) { return Locks.GetOrAdd(Path.GetFullPath(path), (string _) => new object()); } private object GetLock() { return GetLockFor(SavePath.SidecarFilePath); } private static bool Reject(Release1StoryStoreFailureReason reason, string message, out Release1StoryStoreUpdateResult result) { result = new Release1StoryStoreUpdateResult(Succeeded: false, Release1StoryStoreUpdateStatus.Rejected, null, reason, message); return false; } private static Release1StoryStoreFailureReason Map(Release1StoryCodecFailureReason reason) { if (reason != Release1StoryCodecFailureReason.SerializationFailed) { return Release1StoryStoreFailureReason.InvalidStory; } return Release1StoryStoreFailureReason.SerializationFailed; } } } namespace OrganizedCrime.Model { public sealed record CustodyEntryOccurrence(string PlayerId, Guid SessionEpoch, long LoadEpoch, string? Region, string? PropertyCode); public enum CustodyEntryRejectReason { None, MissingIdentity, ExistingCustody, StaleEpoch, ConfirmationMissing, PlayerMismatch, AlreadyCommitted } public sealed record CustodyEntryResult(bool Accepted, CustodyEntryRejectReason RejectReason, string? CorrelationId, CustodyEntryOccurrence? Occurrence); public sealed class CustodyEntryGate { private sealed class PlayerLatch { public bool Armed { get; set; } public long EpisodeCounter { get; set; } } private readonly object _sync = new object(); private readonly Dictionary _latches = new Dictionary(StringComparer.Ordinal); private Guid? _sessionEpoch; private long _loadEpoch; public void ResetForEpoch(Guid sessionEpoch, long loadEpoch) { lock (_sync) { _sessionEpoch = sessionEpoch; _loadEpoch = loadEpoch; _latches.Clear(); } } public void ObserveNotArrested(string playerId, Guid sessionEpoch, long loadEpoch) { if (!IsUsablePlayerId(playerId)) { return; } lock (_sync) { if (IsCurrentEpoch(sessionEpoch, loadEpoch)) { if (!_latches.TryGetValue(playerId, out PlayerLatch value)) { value = new PlayerLatch(); _latches.Add(playerId, value); } value.Armed = true; } } } public CustodyEntryResult TryCommit(CustodyEntryOccurrence occurrence, string confirmedPlayerId, Guid sessionEpoch, long loadEpoch, bool isArrestedAfterCall) { ArgumentNullException.ThrowIfNull(occurrence, "occurrence"); checked { lock (_sync) { if (!IsUsablePlayerId(occurrence.PlayerId) || !IsUsablePlayerId(confirmedPlayerId)) { return Reject(CustodyEntryRejectReason.MissingIdentity); } if (!IsCurrentEpoch(sessionEpoch, loadEpoch) || occurrence.SessionEpoch != sessionEpoch || occurrence.LoadEpoch != loadEpoch) { return Reject(CustodyEntryRejectReason.StaleEpoch); } if (!string.Equals(occurrence.PlayerId, confirmedPlayerId, StringComparison.Ordinal)) { return Reject(CustodyEntryRejectReason.PlayerMismatch); } if (!isArrestedAfterCall) { return Reject(CustodyEntryRejectReason.ConfirmationMissing); } if (!_latches.TryGetValue(occurrence.PlayerId, out PlayerLatch value)) { return Reject(CustodyEntryRejectReason.ExistingCustody); } if (!value.Armed) { return Reject(CustodyEntryRejectReason.AlreadyCommitted); } value.Armed = false; value.EpisodeCounter++; IFormatProvider invariantCulture = CultureInfo.InvariantCulture; DefaultInterpolatedStringHandler handler = new DefaultInterpolatedStringHandler(14, 4, invariantCulture); handler.AppendLiteral("custody/v1/"); handler.AppendFormatted(sessionEpoch, "D"); handler.AppendLiteral("/"); handler.AppendFormatted(loadEpoch); handler.AppendLiteral("/"); handler.AppendFormatted(occurrence.PlayerId); handler.AppendLiteral("/"); handler.AppendFormatted(value.EpisodeCounter); string correlationId = string.Create(invariantCulture, ref handler); return new CustodyEntryResult(Accepted: true, CustodyEntryRejectReason.None, correlationId, occurrence); } } } private bool IsCurrentEpoch(Guid sessionEpoch, long loadEpoch) { if (_sessionEpoch == sessionEpoch) { return _loadEpoch == loadEpoch; } return false; } private static bool IsUsablePlayerId(string? playerId) { if (!string.IsNullOrWhiteSpace(playerId) && !playerId.Contains('\0') && !playerId.Contains('/')) { return !playerId.Contains('\\'); } return false; } private static CustodyEntryResult Reject(CustodyEntryRejectReason reason) { return new CustodyEntryResult(Accepted: false, reason, null, null); } } public readonly record struct FishWarehouseFootprintBounds(float MinimumX, float MaximumX, float MinimumZ, float MaximumZ) { public bool IsValid { get { if (float.IsFinite(MinimumX) && float.IsFinite(MaximumX) && float.IsFinite(MinimumZ) && float.IsFinite(MaximumZ) && MinimumX < MaximumX) { return MinimumZ < MaximumZ; } return false; } } public bool Contains(float x, float z) { if (IsValid && float.IsFinite(x) && float.IsFinite(z) && x >= MinimumX && x <= MaximumX && z >= MinimumZ) { return z <= MaximumZ; } return false; } public FishWarehouseFootprintBounds Inset(float distance) { return new FishWarehouseFootprintBounds(MinimumX + distance, MaximumX - distance, MinimumZ + distance, MaximumZ - distance); } } public readonly record struct FishWarehouseGridCellCoordinate(int X, int Y); public sealed class FishWarehouseBuildableFootprintDefinition { public FishWarehouseFootprintBounds MeasuredBounds { get; } public FishWarehouseFootprintBounds PlacementBounds { get; } public float GridCellSize { get; } public int ClearanceCells { get; } private FishWarehouseBuildableFootprintDefinition(FishWarehouseFootprintBounds measuredBounds, float gridCellSize, int clearanceCells) { MeasuredBounds = measuredBounds; GridCellSize = gridCellSize; ClearanceCells = clearanceCells; PlacementBounds = measuredBounds.Inset(gridCellSize * (float)clearanceCells); } public static FishWarehouseBuildableFootprintDefinition CreateMeasured(float gridCellSize) { if (!float.IsFinite(gridCellSize) || gridCellSize <= 0f) { throw new ArgumentOutOfRangeException("gridCellSize", "The native grid cell size must be finite and positive."); } return new FishWarehouseBuildableFootprintDefinition(new FishWarehouseFootprintBounds(-11.244f, 11.244f, -6.526f, 6.772f), gridCellSize, 1); } public bool ContainsPlacementPoint(FishWarehousePoint point) { return PlacementBounds.Contains(point.X, point.Z); } public bool ContainsPlacementCell(FishWarehousePoint center) { if (!ContainsPlacementPoint(center)) { return false; } float num = GridCellSize / 2f; if (MeasuredBounds.Contains(center.X - num, center.Z - num)) { return MeasuredBounds.Contains(center.X + num, center.Z + num); } return false; } public static bool TryMapLocalPointToCoordinate(FishWarehousePoint point, FishWarehousePoint referenceCenter, float gridCellSize, out FishWarehouseGridCellCoordinate coordinate) { coordinate = default(FishWarehouseGridCellCoordinate); if (!float.IsFinite(point.X) || !float.IsFinite(point.Z) || !float.IsFinite(referenceCenter.X) || !float.IsFinite(referenceCenter.Z) || !float.IsFinite(gridCellSize) || gridCellSize <= 0f) { return false; } coordinate = new FishWarehouseGridCellCoordinate(RoundToNearestInt((point.X - referenceCenter.X) / gridCellSize), RoundToNearestInt((point.Z - referenceCenter.Z) / gridCellSize)); return true; } public static bool ContainsCoordinate(IReadOnlySet allowedCoordinates, FishWarehouseGridCellCoordinate coordinate) { return allowedCoordinates?.Contains(coordinate) ?? false; } public IReadOnlyList GetAllowedCellCoordinates(int referenceCoordinateX, int referenceCoordinateY, float referenceCenterX, float referenceCenterZ) { if (!float.IsFinite(referenceCenterX) || !float.IsFinite(referenceCenterZ)) { throw new ArgumentOutOfRangeException("referenceCenterX", "The reference cell center must be finite."); } float num = GridCellSize / 2f; int num2 = ToCoordinateFloor(MeasuredBounds.MinimumX - num, referenceCenterX, referenceCoordinateX); int num3 = ToCoordinateCeiling(MeasuredBounds.MaximumX + num, referenceCenterX, referenceCoordinateX); int num4 = ToCoordinateFloor(MeasuredBounds.MinimumZ - num, referenceCenterZ, referenceCoordinateY); int num5 = ToCoordinateCeiling(MeasuredBounds.MaximumZ + num, referenceCenterZ, referenceCoordinateY); List list = new List(); for (int i = num4; i <= num5; i++) { for (int j = num2; j <= num3; j++) { FishWarehousePoint center = new FishWarehousePoint(referenceCenterX + (float)(j - referenceCoordinateX) * GridCellSize, 0f, referenceCenterZ + (float)(i - referenceCoordinateY) * GridCellSize); if (ContainsPlacementCell(center)) { list.Add(new FishWarehouseGridCellCoordinate(j, i)); } } } return list; } private int ToCoordinateFloor(float boundary, float referenceCenter, int referenceCoordinate) { return checked((int)MathF.Floor((boundary - referenceCenter) / GridCellSize + (float)referenceCoordinate)); } private int ToCoordinateCeiling(float boundary, float referenceCenter, int referenceCoordinate) { return checked((int)MathF.Ceiling((boundary - referenceCenter) / GridCellSize + (float)referenceCoordinate)); } private static int RoundToNearestInt(float value) { return checked((int)((value >= 0f) ? MathF.Floor(value + 0.5f) : MathF.Ceiling(value - 0.5f))); } } public sealed record FishWarehouseNativeNavigationAgentSetting(int AgentTypeId, string Name); public enum FishWarehouseEmployeeAgentTypeResolutionSource { PersistedId, SettingsName, PropertyDonor, GlobalDonor } public sealed record FishWarehouseEmployeeAgentTypeResolution(int AgentTypeId, string ObservedSettingsName, FishWarehouseEmployeeAgentTypeResolutionSource Source); public enum FishWarehouseEmployeeAgentTypeResolutionStatus { Pending, Succeeded, Failed } public sealed record FishWarehouseEmployeeAgentTypeResolutionResult(FishWarehouseEmployeeAgentTypeResolutionStatus Status, FishWarehouseEmployeeAgentTypeResolution? Resolution, string? FailureReason) { public static FishWarehouseEmployeeAgentTypeResolutionResult Pending() { return new FishWarehouseEmployeeAgentTypeResolutionResult(FishWarehouseEmployeeAgentTypeResolutionStatus.Pending, null, null); } public static FishWarehouseEmployeeAgentTypeResolutionResult Succeeded(FishWarehouseEmployeeAgentTypeResolution resolution) { return new FishWarehouseEmployeeAgentTypeResolutionResult(FishWarehouseEmployeeAgentTypeResolutionStatus.Succeeded, resolution, null); } public static FishWarehouseEmployeeAgentTypeResolutionResult Failed(string reason) { return new FishWarehouseEmployeeAgentTypeResolutionResult(FishWarehouseEmployeeAgentTypeResolutionStatus.Failed, null, reason); } } public static class FishWarehouseEmployeeAgentTypeResolver { private const string EmployeeSettingsName = "Employee"; public static FishWarehouseEmployeeAgentTypeResolutionResult Resolve(int? persistedId, IReadOnlyList settings, IReadOnlyList propertyDonorIds, IReadOnlyList globalDonorIds) { ArgumentNullException.ThrowIfNull(settings, "settings"); ArgumentNullException.ThrowIfNull(propertyDonorIds, "propertyDonorIds"); ArgumentNullException.ThrowIfNull(globalDonorIds, "globalDonorIds"); if (persistedId.HasValue) { int persistedAgentTypeId = persistedId.GetValueOrDefault(); FishWarehouseNativeNavigationAgentSetting fishWarehouseNativeNavigationAgentSetting = settings.FirstOrDefault((FishWarehouseNativeNavigationAgentSetting setting) => setting.AgentTypeId == persistedAgentTypeId); if ((object)fishWarehouseNativeNavigationAgentSetting != null) { return FishWarehouseEmployeeAgentTypeResolutionResult.Succeeded(new FishWarehouseEmployeeAgentTypeResolution(fishWarehouseNativeNavigationAgentSetting.AgentTypeId, fishWarehouseNativeNavigationAgentSetting.Name, FishWarehouseEmployeeAgentTypeResolutionSource.PersistedId)); } } FishWarehouseNativeNavigationAgentSetting[] array = settings.Where((FishWarehouseNativeNavigationAgentSetting setting) => string.Equals(setting.Name, "Employee", StringComparison.Ordinal)).ToArray(); if (array.Length == 1) { FishWarehouseNativeNavigationAgentSetting fishWarehouseNativeNavigationAgentSetting2 = array[0]; return FishWarehouseEmployeeAgentTypeResolutionResult.Succeeded(new FishWarehouseEmployeeAgentTypeResolution(fishWarehouseNativeNavigationAgentSetting2.AgentTypeId, fishWarehouseNativeNavigationAgentSetting2.Name, FishWarehouseEmployeeAgentTypeResolutionSource.SettingsName)); } if (array.Length > 1) { return Ambiguous("settings named Employee", array.Select((FishWarehouseNativeNavigationAgentSetting setting) => setting.AgentTypeId)); } FishWarehouseEmployeeAgentTypeResolutionResult fishWarehouseEmployeeAgentTypeResolutionResult = ResolveDonor(propertyDonorIds, settings, FishWarehouseEmployeeAgentTypeResolutionSource.PropertyDonor, "property-assigned live employee donors"); if (fishWarehouseEmployeeAgentTypeResolutionResult.Status != FishWarehouseEmployeeAgentTypeResolutionStatus.Pending) { return fishWarehouseEmployeeAgentTypeResolutionResult; } return ResolveDonor(globalDonorIds, settings, FishWarehouseEmployeeAgentTypeResolutionSource.GlobalDonor, "global live employee donors"); } private static FishWarehouseEmployeeAgentTypeResolutionResult ResolveDonor(IReadOnlyList donorIds, IReadOnlyList settings, FishWarehouseEmployeeAgentTypeResolutionSource source, string sourceDescription) { int[] array = (from donorId in donorIds.Distinct() orderby donorId select donorId).ToArray(); if (array.Length == 0) { return FishWarehouseEmployeeAgentTypeResolutionResult.Pending(); } if (array.Length > 1) { return Ambiguous(sourceDescription, array); } int agentTypeId = array[0]; FishWarehouseNativeNavigationAgentSetting fishWarehouseNativeNavigationAgentSetting = settings.FirstOrDefault((FishWarehouseNativeNavigationAgentSetting candidate) => candidate.AgentTypeId == agentTypeId); if ((object)fishWarehouseNativeNavigationAgentSetting == null) { return FishWarehouseEmployeeAgentTypeResolutionResult.Pending(); } return FishWarehouseEmployeeAgentTypeResolutionResult.Succeeded(new FishWarehouseEmployeeAgentTypeResolution(agentTypeId, fishWarehouseNativeNavigationAgentSetting.Name, source)); } private static FishWarehouseEmployeeAgentTypeResolutionResult Ambiguous(string source, IEnumerable agentTypeIds) { return FishWarehouseEmployeeAgentTypeResolutionResult.Failed($"Fish Warehouse employee navigation agent settings were ambiguous for {source}; observed IDs={string.Join(',', from id in agentTypeIds.Distinct() orderby id select id)}."); } } public static class FishWarehouseEmployeeHomeAdoptionPlanner { public static int? SelectHomeIndex(IReadOnlyList candidateHomeGuids, IReadOnlyCollection? savedHomeGuids) { if (candidateHomeGuids == null || candidateHomeGuids.Count == 0) { return null; } HashSet hashSet = BuildSavedSet(savedHomeGuids); if (hashSet.Count > 0) { for (int i = 0; i < candidateHomeGuids.Count; i++) { string text = candidateHomeGuids[i]; if (!string.IsNullOrWhiteSpace(text) && hashSet.Contains(text)) { return i; } } } for (int j = 0; j < candidateHomeGuids.Count; j++) { if (!string.IsNullOrWhiteSpace(candidateHomeGuids[j])) { return j; } } return 0; } private static HashSet BuildSavedSet(IReadOnlyCollection? savedHomeGuids) { HashSet hashSet = new HashSet(StringComparer.OrdinalIgnoreCase); if (savedHomeGuids == null) { return hashSet; } foreach (string savedHomeGuid in savedHomeGuids) { if (!string.IsNullOrWhiteSpace(savedHomeGuid)) { hashSet.Add(savedHomeGuid); } } return hashSet; } } public sealed record FishWarehouseEmployeeHomeDefinition(string ItemId, int GridX, int GridY, int Rotation, string PlacementGuid) { public static FishWarehouseEmployeeHomeDefinition Default { get; } = new FishWarehouseEmployeeHomeDefinition("locker", 0, 0, 0, "4ce5b96a-39f5-4ac3-9a05-9fca7a6e8c51"); [CompilerGenerated] private FishWarehouseEmployeeHomeDefinition(FishWarehouseEmployeeHomeDefinition original) { ItemId = original.ItemId; GridX = original.GridX; GridY = original.GridY; Rotation = original.Rotation; PlacementGuid = original.PlacementGuid; } } public sealed record FishWarehouseEmployeePointDefinition(string Name, float X, float Y, float Z); public static class FishWarehouseEmployeeInfrastructureDefinition { public const int Capacity = 10; public const float WallMargin = 1.5f; public static FishWarehouseEmployeePointDefinition NpcSpawnPoint { get; } = new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_NPCSpawnPoint", -9.4f, 0f, 4.8f); public static IReadOnlyList IdlePoints { get; } = new FishWarehouseEmployeePointDefinition[10] { new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_0", -7.8f, 0f, 4.8f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_1", -7.8f, 0f, 2.8f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_2", -7.8f, 0f, 0.8f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_3", -7.8f, 0f, -1.2f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_4", -7.3f, 0f, 10.14f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_5", -6.2f, 0f, 10.18f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_6", -5.42f, 0f, 9.05f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_7", -4.37f, 0f, 9.09f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_8", -4.41f, 0f, 10.05f), new FishWarehouseEmployeePointDefinition("OC_FishWarehouse_EmployeeIdle_9", -4.46f, 0f, 11.45f) }; public static bool IsInsideRoom(FishWarehouseEmployeePointDefinition point) { if (MathF.Abs(point.X) <= 10.1f) { return MathF.Abs(point.Z) <= 5.4f; } return false; } public static bool IsValidEmployeeIndex(int index) { if (index >= 0) { return index < 10; } return false; } public static bool HasValidLayout() { return HasValidLayout(IdlePoints); } public static bool HasValidLayout(IEnumerable? points) { if (IsInsideRoom(NpcSpawnPoint) && FishWarehousePropertyBoundsDefinition.Contains(NpcSpawnPoint.X, NpcSpawnPoint.Y, NpcSpawnPoint.Z)) { return FishWarehouseEmployeePointLayoutValidator.Validate(10, points, FishWarehouseEmployeePointLayoutRules.Default).Succeeded; } return false; } } public static class FishWarehouseEmployeePlacementDefinition { public static bool IsSameNativeObject(int leftInstanceId, int rightInstanceId) { if (leftInstanceId != 0) { return leftInstanceId == rightInstanceId; } return false; } } public sealed record FishWarehouseEmployeePointExclusionRegion(string Name, float MinimumX, float MaximumX, float MinimumZ, float MaximumZ) { public bool IsValid { get { if (!string.IsNullOrWhiteSpace(Name) && float.IsFinite(MinimumX) && float.IsFinite(MaximumX) && float.IsFinite(MinimumZ) && float.IsFinite(MaximumZ) && MinimumX < MaximumX) { return MinimumZ < MaximumZ; } return false; } } public bool Contains(float x, float z) { if (IsValid && x >= MinimumX && x <= MaximumX && z >= MinimumZ) { return z <= MaximumZ; } return false; } } public sealed record FishWarehouseEmployeePointLayoutRules(float MinimumPairwiseSpacing, IReadOnlyList WallExclusions, IReadOnlyList ApertureExclusions) { public static FishWarehouseEmployeePointLayoutRules Default { get; } = new FishWarehouseEmployeePointLayoutRules(0.75f, new FishWarehouseEmployeePointExclusionRegion[4] { new FishWarehouseEmployeePointExclusionRegion("west-wall-clearance", -11.6f, -10.1f, -6.9f, 6.9f), new FishWarehouseEmployeePointExclusionRegion("east-wall-clearance", 10.1f, 11.6f, -6.9f, 6.9f), new FishWarehouseEmployeePointExclusionRegion("south-wall-clearance", -11.6f, 11.6f, -6.9f, -5.4f), new FishWarehouseEmployeePointExclusionRegion("north-wall-clearance", -11.6f, 11.6f, 5.4f, 6.9f) }, new FishWarehouseEmployeePointExclusionRegion[2] { new FishWarehouseEmployeePointExclusionRegion("garage-aperture-clearance", -11.6f, 11.6f, 6.9f, 7.8f), new FishWarehouseEmployeePointExclusionRegion("personnel-aperture-clearance", -11.6f, 11.6f, -7.8f, -6.9f) }); [CompilerGenerated] private FishWarehouseEmployeePointLayoutRules(FishWarehouseEmployeePointLayoutRules original) { MinimumPairwiseSpacing = original.MinimumPairwiseSpacing; WallExclusions = original.WallExclusions; ApertureExclusions = original.ApertureExclusions; } } public sealed record FishWarehouseEmployeePointLayoutValidationResult(bool Succeeded, IReadOnlyList FailureReasons); public static class FishWarehouseEmployeePointLayoutValidator { private const string IdlePointNamePrefix = "OC_FishWarehouse_EmployeeIdle_"; public static FishWarehouseEmployeePointLayoutValidationResult Validate(int capacity, IEnumerable? points, FishWarehouseEmployeePointLayoutRules? rules) { List failures = new List(); AddFailureIf(failures, capacity <= 0, "invalid-capacity"); if (!IsValidRules(rules)) { AddFailure(failures, "malformed-layout-rules"); } if (points == null) { AddFailure(failures, "missing-idle-points"); return Complete(failures); } FishWarehouseEmployeePointDefinition[] array; try { array = points.ToArray(); } catch (Exception) { AddFailure(failures, "malformed-idle-points"); return Complete(failures); } if (array.Length < capacity) { AddFailure(failures, "too-few-idle-points"); } if (array.Length > capacity) { AddFailure(failures, "too-many-idle-points"); } if (array.Length != capacity) { AddFailure(failures, "capacity-count-mismatch"); } for (int i = 0; i < array.Length; i++) { FishWarehouseEmployeePointDefinition fishWarehouseEmployeePointDefinition = array[i]; if ((object)fishWarehouseEmployeePointDefinition == null) { AddFailure(failures, "malformed-idle-point"); continue; } if (!float.IsFinite(fishWarehouseEmployeePointDefinition.X) || !float.IsFinite(fishWarehouseEmployeePointDefinition.Y) || !float.IsFinite(fishWarehouseEmployeePointDefinition.Z)) { AddFailure(failures, "non-finite-idle-point"); } if (string.IsNullOrWhiteSpace(fishWarehouseEmployeePointDefinition.Name)) { AddFailure(failures, "malformed-idle-point"); continue; } if (!TryGetIndex(fishWarehouseEmployeePointDefinition.Name, out var index) || index < 0 || index >= capacity) { AddFailure(failures, "invalid-idle-point-index"); } if (fishWarehouseEmployeePointDefinition.Name != GetExpectedName(i)) { AddFailure(failures, "idle-point-order"); } } bool condition = array.Where((FishWarehouseEmployeePointDefinition fishWarehouseEmployeePointDefinition2) => (object)fishWarehouseEmployeePointDefinition2 != null && !string.IsNullOrWhiteSpace(fishWarehouseEmployeePointDefinition2.Name)).GroupBy((FishWarehouseEmployeePointDefinition fishWarehouseEmployeePointDefinition2) => fishWarehouseEmployeePointDefinition2.Name, StringComparer.Ordinal).Any((IGrouping group) => group.Count() > 1); AddFailureIf(failures, condition, "duplicate-idle-point-name"); if (IsValidRules(rules)) { for (int num = 0; num < array.Length; num++) { if (!HasFiniteCoordinates(array[num])) { continue; } for (int num2 = num + 1; num2 < array.Length; num2++) { if (HasFiniteCoordinates(array[num2])) { float num3 = array[num].X - array[num2].X; float num4 = array[num].Z - array[num2].Z; if (num3 * num3 + num4 * num4 < rules.MinimumPairwiseSpacing * rules.MinimumPairwiseSpacing) { AddFailure(failures, "idle-point-spacing"); } } } } foreach (FishWarehouseEmployeePointDefinition point in array.OfType()) { if (HasFiniteCoordinates(point)) { if (rules.WallExclusions.Any((FishWarehouseEmployeePointExclusionRegion region) => region.Contains(point.X, point.Z))) { AddFailure(failures, "wall-clearance"); } if (rules.ApertureExclusions.Any((FishWarehouseEmployeePointExclusionRegion region) => region.Contains(point.X, point.Z))) { AddFailure(failures, "aperture-clearance"); } } } } return Complete(failures); } private static bool IsValidRules(FishWarehouseEmployeePointLayoutRules? rules) { if ((object)rules != null && float.IsFinite(rules.MinimumPairwiseSpacing) && rules.MinimumPairwiseSpacing > 0f && IsValidRegions(rules.WallExclusions)) { return IsValidRegions(rules.ApertureExclusions); } return false; } private static bool IsValidRegions(IReadOnlyList? regions) { return regions?.All((FishWarehouseEmployeePointExclusionRegion region) => region?.IsValid ?? false) ?? false; } private static bool HasFiniteCoordinates(FishWarehouseEmployeePointDefinition? point) { if ((object)point != null && float.IsFinite(point.X) && float.IsFinite(point.Y)) { return float.IsFinite(point.Z); } return false; } private static string GetExpectedName(int index) { return $"{"OC_FishWarehouse_EmployeeIdle_"}{index}"; } private static bool TryGetIndex(string name, out int index) { index = -1; if (name.StartsWith("OC_FishWarehouse_EmployeeIdle_", StringComparison.Ordinal)) { int length = "OC_FishWarehouse_EmployeeIdle_".Length; return int.TryParse(name.Substring(length, name.Length - length), out index); } return false; } private static void AddFailureIf(ICollection failures, bool condition, string reason) { if (condition) { AddFailure(failures, reason); } } private static void AddFailure(ICollection failures, string reason) { if (!failures.Contains(reason)) { failures.Add(reason); } } private static FishWarehouseEmployeePointLayoutValidationResult Complete(List failures) { return new FishWarehouseEmployeePointLayoutValidationResult(failures.Count == 0, failures.AsReadOnly()); } } public sealed record FishWarehouseFrontageBounds(float MinX, float MaxX, float MinZ, float MaxZ) { public bool Contains(FishWarehouseDockDefinition dock) { if (dock.CenterX - FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).X >= MinX && dock.CenterX + FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).X <= MaxX && dock.CenterZ - FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).Z >= MinZ) { return dock.CenterZ + FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).Z <= MaxZ; } return false; } public bool Overlaps(FishWarehouseFrontageBounds other) { if (MinX <= other.MaxX && MaxX >= other.MinX && MinZ <= other.MaxZ) { return MaxZ >= other.MinZ; } return false; } } public sealed record FishWarehouseFrontageCandidate(string Path, bool ActiveSelf, bool IsStatic, bool HasRenderer, bool HasCollider, IReadOnlyCollection ComponentTypeNames, FishWarehouseFrontageBounds Bounds, bool HasProtectedAncestor = false, bool HasUnknownBehaviour = false); public sealed record FishWarehouseFrontageCandidateState(string Path, bool OriginalActiveSelf); public enum FishWarehouseFrontageClearanceDecision { Clear, OutsideEnvelope, Protected, NotEligible } public static class FishWarehouseFrontageClearanceDefinition { private const string DocksRegionPath = "Map/Hyland Point/Region_Docks"; public const float ClearanceMargin = 0.5f; private static readonly string[] ApprovedShippingContainerNames = new string[4] { "Red Shipping Container", "Red Shipping Container (1)", "Blue Shipping Container", "Green Shipping Container" }; private static readonly string[] ProtectedTypeTokens = new string[9] { "LandVehicle", "ParkingLot", "LoadingDock", "VehicleDetector", "NPC", "DealLocation", "TransitEntity", "NetworkObject", "Property" }; public static FishWarehouseFrontageBounds DockEnvelope { get; } = CreateDockEnvelope(); public static IReadOnlyList DockClearanceBounds { get; } = Array.AsReadOnly(FishWarehouseLoadingDockDefinition.Docks.Select(CreateDockClearanceBounds).ToArray()); public static FishWarehouseFrontageBounds ClearanceEnvelope { get; } = new FishWarehouseFrontageBounds(DockEnvelope.MinX - 0.5f, DockEnvelope.MaxX + 0.5f, DockEnvelope.MinZ - 0.5f, DockEnvelope.MaxZ + 0.5f); public static FishWarehouseFrontageClearanceDecision Evaluate(FishWarehouseFrontageCandidate candidate) { ArgumentNullException.ThrowIfNull(candidate, "candidate"); ArgumentNullException.ThrowIfNull(candidate.ComponentTypeNames, "candidate.ComponentTypeNames"); ArgumentNullException.ThrowIfNull(candidate.Bounds, "candidate.Bounds"); bool flag = IsApprovedShippingContainerPath(candidate.Path); if (!flag && !IsInsideAnyDockClearanceScope(candidate.Bounds)) { return FishWarehouseFrontageClearanceDecision.OutsideEnvelope; } if (candidate.HasProtectedAncestor || IsProtectedPath(candidate.Path) || candidate.ComponentTypeNames.Any(IsProtectedTypeName)) { return FishWarehouseFrontageClearanceDecision.Protected; } if (!flag && candidate.HasUnknownBehaviour) { return FishWarehouseFrontageClearanceDecision.NotEligible; } if (!(candidate.IsStatic || flag) || (!candidate.HasRenderer && !candidate.HasCollider) || !candidate.ComponentTypeNames.All(IsAllowedStaticComponentTypeName)) { return FishWarehouseFrontageClearanceDecision.NotEligible; } return FishWarehouseFrontageClearanceDecision.Clear; } public static bool IsApprovedShippingContainerPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return false; } string text = "Map/Hyland Point/Region_Docks/"; if (!path.StartsWith(text, StringComparison.Ordinal)) { return false; } int length = text.Length; string text2 = path.Substring(length, path.Length - length); if (!text2.Contains('/')) { return ApprovedShippingContainerNames.Contains(text2, StringComparer.Ordinal); } return false; } public static IReadOnlyList CaptureOriginalStates(IEnumerable candidates) { ArgumentNullException.ThrowIfNull(candidates, "candidates"); return Array.AsReadOnly(candidates.ToArray()); } public static bool CanRestore(IEnumerable states, int restoredCount) { ArgumentNullException.ThrowIfNull(states, "states"); return restoredCount == states.Count(); } public static bool IsProtectedTypeName(string typeName) { if (string.IsNullOrWhiteSpace(typeName)) { return false; } return ProtectedTypeTokens.Any((string token) => typeName.Contains(token, StringComparison.OrdinalIgnoreCase)); } public static bool IsProtectedPath(string path) { if (string.IsNullOrWhiteSpace(path)) { return false; } if (!path.Contains("parking", StringComparison.OrdinalIgnoreCase)) { return path.Contains("billy", StringComparison.OrdinalIgnoreCase); } return true; } public static IReadOnlyList GetOverlappingDockIndices(FishWarehouseFrontageBounds bounds) { ArgumentNullException.ThrowIfNull(bounds, "bounds"); return Array.AsReadOnly((from entry in DockClearanceBounds.Select((FishWarehouseFrontageBounds clearance, int index) => (clearance: clearance, index: index)) where entry.clearance.Overlaps(bounds) select entry.index).ToArray()); } public static bool IsInsideAnyDockClearanceScope(FishWarehouseFrontageBounds bounds) { ArgumentNullException.ThrowIfNull(bounds, "bounds"); return DockClearanceBounds.Any((FishWarehouseFrontageBounds clearance) => clearance.Overlaps(bounds)); } public static bool IsAllowedStaticComponentTypeName(string typeName) { if (string.IsNullOrWhiteSpace(typeName)) { return false; } int num = typeName.LastIndexOf('.') + 1; switch (typeName.Substring(num, typeName.Length - num)) { case "Collider": case "LODGroup": case "Renderer": case "MeshCollider": case "MeshRenderer": case "Transform": case "MeshFilter": case "SkinnedMeshRenderer": case "BoxCollider": case "SphereCollider": case "CapsuleCollider": return true; default: return false; } } private static FishWarehouseFrontageBounds CreateDockEnvelope() { IReadOnlyList docks = FishWarehouseLoadingDockDefinition.Docks; return new FishWarehouseFrontageBounds(docks.Min((FishWarehouseDockDefinition dock) => dock.CenterX - FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).X), docks.Max((FishWarehouseDockDefinition dock) => dock.CenterX + FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).X), docks.Min((FishWarehouseDockDefinition dock) => dock.CenterZ - FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).Z), docks.Max((FishWarehouseDockDefinition dock) => dock.CenterZ + FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock).Z)); } private static FishWarehouseFrontageBounds CreateDockClearanceBounds(FishWarehouseDockDefinition dock) { (float, float) axisAlignedHalfExtents = FishWarehouseLoadingDockDefinition.GetAxisAlignedHalfExtents(dock); return new FishWarehouseFrontageBounds(dock.CenterX - axisAlignedHalfExtents.Item1 - 0.5f, dock.CenterX + axisAlignedHalfExtents.Item1 + 0.5f, dock.CenterZ - axisAlignedHalfExtents.Item2 - 0.5f, dock.CenterZ + axisAlignedHalfExtents.Item2 + 0.5f); } } public enum FishWarehouseGarageCrossingPhase { Idle, Gliding } public enum FishWarehouseGarageCrossingActionKind { None, BeginGlide, StepGlide, CompleteGlide } public readonly record struct FishWarehouseGarageCrossingSample(bool IsOnGarageLink, float Speed, FishWarehouseNativeNavigationVector Position, FishWarehouseNativeNavigationVector LinkExit); public sealed record FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingActionKind Kind, FishWarehouseNativeNavigationVector GlideTarget = default(FishWarehouseNativeNavigationVector)) { public static readonly FishWarehouseGarageCrossingDecision None = new FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingActionKind.None); [CompilerGenerated] private FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingDecision original) { Kind = original.Kind; GlideTarget = original.GlideTarget; } } public sealed class FishWarehouseGarageCrossingAgentState { public FishWarehouseGarageCrossingPhase Phase { get; set; } public int StalledTicks { get; set; } public void Reset() { Phase = FishWarehouseGarageCrossingPhase.Idle; StalledTicks = 0; } } public static class FishWarehouseGarageCrossingDefinition { public const float StallSpeedThreshold = 0.15f; public const int StallDwellTicks = 30; public const float GlideSpeed = 1.45f; public const float GlideArriveEpsilon = 0.2f; public static FishWarehouseGarageCrossingDecision Decide(FishWarehouseGarageCrossingAgentState state, FishWarehouseGarageCrossingSample sample, float deltaTime, bool hostAuthority) { if (state == null || !hostAuthority || deltaTime <= 0f) { return FishWarehouseGarageCrossingDecision.None; } if (state.Phase == FishWarehouseGarageCrossingPhase.Gliding) { return StepGlideToward(state, sample, deltaTime); } if (!sample.IsOnGarageLink) { state.StalledTicks = 0; return FishWarehouseGarageCrossingDecision.None; } state.StalledTicks = ((sample.Speed < 0.15f) ? (state.StalledTicks + 1) : 0); if (state.StalledTicks < 30) { return FishWarehouseGarageCrossingDecision.None; } state.Phase = FishWarehouseGarageCrossingPhase.Gliding; state.StalledTicks = 0; return new FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingActionKind.BeginGlide); } private static FishWarehouseGarageCrossingDecision StepGlideToward(FishWarehouseGarageCrossingAgentState state, FishWarehouseGarageCrossingSample sample, float deltaTime) { if (!sample.IsOnGarageLink) { state.Reset(); return new FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingActionKind.CompleteGlide, sample.Position); } FishWarehouseNativeNavigationVector fishWarehouseNativeNavigationVector = MoveTowards(sample.Position, sample.LinkExit, 1.45f * deltaTime); if (Distance(fishWarehouseNativeNavigationVector, sample.LinkExit) <= 0.2f) { state.Reset(); return new FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingActionKind.CompleteGlide, sample.LinkExit); } return new FishWarehouseGarageCrossingDecision(FishWarehouseGarageCrossingActionKind.StepGlide, fishWarehouseNativeNavigationVector); } public static float Distance(FishWarehouseNativeNavigationVector a, FishWarehouseNativeNavigationVector b) { float num = a.X - b.X; float num2 = a.Y - b.Y; float num3 = a.Z - b.Z; return MathF.Sqrt(num * num + num2 * num2 + num3 * num3); } public static FishWarehouseNativeNavigationVector MoveTowards(FishWarehouseNativeNavigationVector current, FishWarehouseNativeNavigationVector target, float maxDistanceDelta) { float num = target.X - current.X; float num2 = target.Y - current.Y; float num3 = target.Z - current.Z; float num4 = MathF.Sqrt(num * num + num2 * num2 + num3 * num3); if (num4 <= maxDistanceDelta || num4 <= 1E-06f) { return target; } float num5 = maxDistanceDelta / num4; return new FishWarehouseNativeNavigationVector(current.X + num * num5, current.Y + num2 * num5, current.Z + num3 * num5); } public static FishWarehouseNativeNavigationVector SelectLinkExit(FishWarehouseNativeNavigationVector position, FishWarehouseNativeNavigationVector linkStart, FishWarehouseNativeNavigationVector linkEnd) { if (!(Distance(position, linkStart) >= Distance(position, linkEnd))) { return linkEnd; } return linkStart; } } public readonly record struct FishWarehouseAnchorLocalBounds(float MinimumX, float MaximumX, float MinimumY, float MaximumY, float MinimumZ, float MaximumZ) { public bool IsValid { get { if (float.IsFinite(MinimumX) && float.IsFinite(MaximumX) && float.IsFinite(MinimumY) && float.IsFinite(MaximumY) && float.IsFinite(MinimumZ) && float.IsFinite(MaximumZ) && MinimumX <= MaximumX && MinimumY <= MaximumY) { return MinimumZ <= MaximumZ; } return false; } } public bool Intersects(FishWarehouseAnchorLocalBounds other) { if (IsValid && other.IsValid && MinimumX < other.MaximumX && MaximumX > other.MinimumX && MinimumY < other.MaximumY && MaximumY > other.MinimumY && MinimumZ < other.MaximumZ) { return MaximumZ > other.MinimumZ; } return false; } } public sealed record FishWarehouseGarageOpeningCandidate(string RootPath, string PanelIdentity, FishWarehouseAnchorLocalBounds PanelBounds, FishWarehousePhysicalPanelPlane PhysicalPanelPlane, IReadOnlyList SurvivingNonTargetColliders, bool HasFloorSupport); public sealed record FishWarehouseNavigationHorizontalBounds(float MinimumX, float MaximumX) { public float Width => MaximumX - MinimumX; } public sealed record FishWarehouseTransitionPrism(float MinimumX, float MaximumX, float MinimumY, float MaximumY, float MinimumZ, float MaximumZ) { public float Depth => MaximumZ - MinimumZ; } public sealed record FishWarehouseGarageOpeningProjection(FishWarehouseWallOpening Opening, float RawMinimumX, float RawMaximumX, float Lintel, FishWarehouseTransitionPrism TransitionPrism, FishWarehouseNavigationHorizontalBounds NavigationBounds) { public float NavigationCenterX => (NavigationBounds.MinimumX + NavigationBounds.MaximumX) / 2f; } public static class FishWarehouseGarageOpeningDefinition { public const string ApprovedGarageDoorRootPath = "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse/GarageDoor (1)"; public const string ApprovedAlternateRootPath = "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse/GarageDoor_Other"; private const string ApprovedGarageDoorPanelIdentity = "GarageDoor (1)"; private const string ApprovedAlternatePanelIdentity = "GarageDoor_Other"; private const float PanelClearance = 0.1f; private const float BoundarySnapTolerance = 0.01f; private const float MinimumLintel = 0.3f; private const float MinimumResidualWallMargin = 0.5f; public static bool TryProject(IEnumerable? candidates, out FishWarehouseGarageOpeningProjection? projection) { projection = null; if (candidates == null) { return false; } FishWarehouseGarageOpeningCandidate[] array = candidates.ToArray(); if (array.Length != 2) { return false; } FishWarehouseGarageOpeningCandidate fishWarehouseGarageOpeningCandidate = null; FishWarehouseGarageOpeningCandidate fishWarehouseGarageOpeningCandidate2 = null; FishWarehouseGarageOpeningCandidate[] array2 = array; foreach (FishWarehouseGarageOpeningCandidate fishWarehouseGarageOpeningCandidate3 in array2) { if (!fishWarehouseGarageOpeningCandidate3.PanelBounds.IsValid || fishWarehouseGarageOpeningCandidate3.SurvivingNonTargetColliders == null || !FishWarehouseGarageOpeningGeometryDefinition.IsNorthWallPhysicalPlane(fishWarehouseGarageOpeningCandidate3.PanelBounds, fishWarehouseGarageOpeningCandidate3.PhysicalPanelPlane) || !fishWarehouseGarageOpeningCandidate3.HasFloorSupport) { return false; } if (fishWarehouseGarageOpeningCandidate3.RootPath == "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse/GarageDoor (1)" && fishWarehouseGarageOpeningCandidate3.PanelIdentity == "GarageDoor (1)") { if ((object)fishWarehouseGarageOpeningCandidate != null) { return false; } fishWarehouseGarageOpeningCandidate = fishWarehouseGarageOpeningCandidate3; continue; } if (fishWarehouseGarageOpeningCandidate3.RootPath == "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse/GarageDoor_Other" && fishWarehouseGarageOpeningCandidate3.PanelIdentity == "GarageDoor_Other") { if ((object)fishWarehouseGarageOpeningCandidate2 != null) { return false; } fishWarehouseGarageOpeningCandidate2 = fishWarehouseGarageOpeningCandidate3; continue; } return false; } if ((object)fishWarehouseGarageOpeningCandidate == null || (object)fishWarehouseGarageOpeningCandidate2 == null || !FishWarehouseGarageOpeningGeometryDefinition.AreCoincidentPhysicalFaces(fishWarehouseGarageOpeningCandidate.PanelBounds, fishWarehouseGarageOpeningCandidate.PhysicalPanelPlane, fishWarehouseGarageOpeningCandidate2.PanelBounds, fishWarehouseGarageOpeningCandidate2.PhysicalPanelPlane)) { return false; } FishWarehouseAnchorLocalBounds panelBounds = fishWarehouseGarageOpeningCandidate.PanelBounds; float rawMinimumX = panelBounds.MinimumX - 0.1f; float rawMaximumX = panelBounds.MaximumX + 0.1f; if (!TryClampHorizontalBounds(rawMinimumX, rawMaximumX, out var minimumX, out var maximumX)) { return false; } float num = MathF.Max(0f, panelBounds.MinimumY - 0.1f); float num2 = panelBounds.MaximumY + 0.1f; float num3 = 5.8f - num2; if (num > 0f || num >= num2 || num2 > 5.8f || num3 < 0.3f) { return false; } float z = fishWarehouseGarageOpeningCandidate.PhysicalPanelPlane.AnchorLocalCenter.Z; float num4 = z - 6.9f; if (num4 <= 0f || num4 > 1f) { return false; } float num5 = minimumX + 11.6f; float num6 = 11.6f - maximumX; bool flag = MathF.Abs(minimumX + 11.6f) <= 0.01f; bool flag2 = MathF.Abs(maximumX - 11.6f) <= 0.01f; if (flag) { if (num6 < 0.5f) { return false; } } else if (flag2) { if (num5 < 0.5f) { return false; } } else if (num5 < 0.5f || num6 < 0.5f) { return false; } FishWarehouseWallOpening opening = new FishWarehouseWallOpening(FishWarehouseInteriorDoorSide.North, minimumX, maximumX, num, num2, flag, flag2, "garage"); FishWarehouseTransitionPrism fishWarehouseTransitionPrism = new FishWarehouseTransitionPrism(minimumX, maximumX, num, num2, 6.9f, z); array2 = array; foreach (FishWarehouseGarageOpeningCandidate fishWarehouseGarageOpeningCandidate4 in array2) { if (!FishWarehouseGarageOpeningClearanceDefinition.IsTraversableVolumeClear(fishWarehouseTransitionPrism, fishWarehouseGarageOpeningCandidate4.SurvivingNonTargetColliders) || !FishWarehouseGarageOpeningClearanceDefinition.IsFloorBandClear(fishWarehouseTransitionPrism, fishWarehouseGarageOpeningCandidate4.SurvivingNonTargetColliders)) { return false; } } FishWarehouseNavigationHorizontalBounds navigationBounds = new FishWarehouseNavigationHorizontalBounds(minimumX + 11.6f, maximumX + 11.6f); projection = new FishWarehouseGarageOpeningProjection(opening, rawMinimumX, rawMaximumX, num3, fishWarehouseTransitionPrism, navigationBounds); return true; } private static bool TryClampHorizontalBounds(float rawMinimumX, float rawMaximumX, out float minimumX, out float maximumX) { float num = -11.6f; float num2 = 11.6f; if (rawMaximumX <= num || rawMinimumX >= num2) { minimumX = 0f; maximumX = 0f; return false; } minimumX = SnapBoundary(MathF.Max(rawMinimumX, num), num); maximumX = SnapBoundary(MathF.Min(rawMaximumX, num2), num2); if (minimumX < num || maximumX > num2 || minimumX >= maximumX) { return false; } return true; } private static float SnapBoundary(float value, float edge) { if (!(MathF.Abs(value - edge) <= 0.01f)) { return value; } return edge; } } public readonly record struct FishWarehouseGarageVector(float X, float Y, float Z) { public bool IsFinite { get { if (float.IsFinite(X) && float.IsFinite(Y)) { return float.IsFinite(Z); } return false; } } public float Length => MathF.Sqrt(X * X + Y * Y + Z * Z); } public readonly record struct FishWarehouseGarageQuaternion(float X, float Y, float Z, float W) { public bool IsFinite { get { if (float.IsFinite(X) && float.IsFinite(Y) && float.IsFinite(Z)) { return float.IsFinite(W); } return false; } } public float Length => MathF.Sqrt(X * X + Y * Y + Z * Z + W * W); } public readonly record struct FishWarehouseGaragePanelAxisEvidence(FishWarehouseGarageQuaternion PrimaryRotationRelativeToAnchor, FishWarehouseGarageQuaternion SelectedChildRotationRelativeToPrimary, FishWarehouseGarageVector PhysicalAxisExtents); public readonly record struct FishWarehouseGaragePanelAxes(FishWarehouseGarageVector Right, FishWarehouseGarageVector Up, FishWarehouseGarageVector Forward); public static class FishWarehouseGaragePhysicalPanelDefinition { private const float MinimumDistinctAxisExtent = 0.01f; private const float MaximumThinAxisRatio = 0.25f; public static bool TryGetPhysicalAxisExtents(FishWarehouseGarageVector meshLocalExtents, FishWarehouseGarageVector lossyScale, out FishWarehouseGarageVector physicalAxisExtents) { physicalAxisExtents = default(FishWarehouseGarageVector); if (!meshLocalExtents.IsFinite || !lossyScale.IsFinite || meshLocalExtents.X < 0f || meshLocalExtents.Y < 0f || meshLocalExtents.Z < 0f) { return false; } physicalAxisExtents = new FishWarehouseGarageVector(MathF.Abs(meshLocalExtents.X * lossyScale.X), MathF.Abs(meshLocalExtents.Y * lossyScale.Y), MathF.Abs(meshLocalExtents.Z * lossyScale.Z)); return physicalAxisExtents.IsFinite; } public static bool TryDeriveAnchorLocalAxes(FishWarehouseGaragePanelAxisEvidence evidence, out FishWarehouseGaragePanelAxes axes) { axes = default(FishWarehouseGaragePanelAxes); if (!TryNormalize(evidence.PrimaryRotationRelativeToAnchor, out var normalized) || !TryNormalize(evidence.SelectedChildRotationRelativeToPrimary, out var normalized2)) { return false; } FishWarehouseGarageQuaternion rotation = Multiply(normalized, normalized2); axes = new FishWarehouseGaragePanelAxes(Rotate(rotation, new FishWarehouseGarageVector(1f, 0f, 0f)), Rotate(rotation, new FishWarehouseGarageVector(0f, 1f, 0f)), Rotate(rotation, new FishWarehouseGarageVector(0f, 0f, 1f))); if (axes.Right.IsFinite && axes.Up.IsFinite) { return axes.Forward.IsFinite; } return false; } public static bool TryDerivePhysicalNormal(FishWarehouseGaragePanelAxisEvidence evidence, out FishWarehouseGarageVector normal) { normal = default(FishWarehouseGarageVector); if (!TrySelectThinAxis(evidence.PhysicalAxisExtents, out var axis) || !TryDeriveAnchorLocalAxes(evidence, out var axes)) { return false; } normal = axis switch { 0 => axes.Right, 1 => axes.Up, _ => axes.Forward, }; if (normal.IsFinite) { return normal.Length > 0f; } return false; } private static bool TrySelectThinAxis(FishWarehouseGarageVector extents, out int axis) { axis = -1; if (!extents.IsFinite || extents.X < 0f || extents.Y < 0f || extents.Z < 0f) { return false; } float[] array = new float[3] { extents.X, extents.Y, extents.Z }; int smallest = 0; for (int i = 1; i < array.Length; i++) { if (array[i] < array[smallest]) { smallest = i; } } float num = array.Where((float _, int index) => index != smallest).Min(); if (num < 0.01f || array[smallest] + 0.01f >= num || array[smallest] > num * 0.25f) { return false; } axis = smallest; return true; } private static bool TryNormalize(FishWarehouseGarageQuaternion value, out FishWarehouseGarageQuaternion normalized) { normalized = default(FishWarehouseGarageQuaternion); float length = value.Length; if (!value.IsFinite || !float.IsFinite(length) || length <= 0f) { return false; } normalized = new FishWarehouseGarageQuaternion(value.X / length, value.Y / length, value.Z / length, value.W / length); return true; } private static FishWarehouseGarageQuaternion Multiply(FishWarehouseGarageQuaternion left, FishWarehouseGarageQuaternion right) { return new FishWarehouseGarageQuaternion(left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y, left.W * right.Y - left.X * right.Z + left.Y * right.W + left.Z * right.X, left.W * right.Z + left.X * right.Y - left.Y * right.X + left.Z * right.W, left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z); } private static FishWarehouseGarageVector Rotate(FishWarehouseGarageQuaternion rotation, FishWarehouseGarageVector vector) { FishWarehouseGarageQuaternion fishWarehouseGarageQuaternion = Multiply(Multiply(rotation, new FishWarehouseGarageQuaternion(vector.X, vector.Y, vector.Z, 0f)), new FishWarehouseGarageQuaternion(0f - rotation.X, 0f - rotation.Y, 0f - rotation.Z, rotation.W)); return new FishWarehouseGarageVector(fishWarehouseGarageQuaternion.X, fishWarehouseGarageQuaternion.Y, fishWarehouseGarageQuaternion.Z); } } public readonly record struct FishWarehouseWorldBounds(FishWarehouseGarageVector Center, FishWarehouseGarageVector Extents) { public bool IsValid { get { if (Center.IsFinite && Extents.IsFinite && Extents.X >= 0f && Extents.Y >= 0f) { return Extents.Z >= 0f; } return false; } } } public readonly record struct FishWarehouseAnchorFrame(FishWarehouseGarageVector Position, float yawDegrees); public readonly record struct FishWarehouseGarageRuntimeAnchor(FishWarehouseGarageVector WorldPosition, FishWarehouseGarageQuaternion WorldRotation); public readonly record struct FishWarehouseGaragePanelRuntimeEvidence(FishWarehouseWorldBounds WorldRendererBounds, FishWarehouseGarageVector MeshLocalCenter, FishWarehouseGarageVector MeshLocalExtents, FishWarehouseGarageVector RendererLossyScale, FishWarehouseGarageVector RendererWorldPosition, FishWarehouseGarageQuaternion PanelRootWorldRotation, FishWarehouseGarageQuaternion RendererWorldRotation, bool IsPartOfStaticBatch = false); public enum FishWarehouseGaragePanelPlaneSource { MeshCenterAndThinAxis, StaticBatchRendererBoundsCenterAndPanelRootUp } public readonly record struct FishWarehouseGaragePanelRuntimeMapping(FishWarehouseAnchorLocalBounds SafetyEnvelope, FishWarehousePhysicalPanelPlane PhysicalPanelPlane, FishWarehouseGaragePanelPlaneSource PlaneSource); public static class FishWarehouseGarageRuntimeEvidenceMapper { public static bool TryMap(FishWarehouseGarageRuntimeAnchor anchor, FishWarehouseGaragePanelRuntimeEvidence evidence, out FishWarehouseGaragePanelRuntimeMapping mapping) { mapping = default(FishWarehouseGaragePanelRuntimeMapping); if (!anchor.WorldPosition.IsFinite || !evidence.WorldRendererBounds.IsValid || !TryNormalize(anchor.WorldRotation, out var normalized) || !TryNormalize(evidence.PanelRootWorldRotation, out var normalized2) || !TryNormalize(evidence.RendererWorldRotation, out var normalized3)) { return false; } FishWarehouseAnchorLocalBounds safetyEnvelope = ToAnchorLocalBounds(evidence.WorldRendererBounds, anchor, normalized); if (!safetyEnvelope.IsValid) { return false; } FishWarehouseGarageQuaternion fishWarehouseGarageQuaternion = Multiply(Inverse(normalized), normalized2); FishWarehouseGarageVector anchorLocalCenter; FishWarehouseGarageVector normal; FishWarehouseGaragePanelPlaneSource planeSource; if (evidence.IsPartOfStaticBatch) { anchorLocalCenter = ToAnchorLocalPoint(evidence.WorldRendererBounds.Center, anchor, normalized); normal = Rotate(fishWarehouseGarageQuaternion, new FishWarehouseGarageVector(0f, 1f, 0f)); planeSource = FishWarehouseGaragePanelPlaneSource.StaticBatchRendererBoundsCenterAndPanelRootUp; } else { if (!evidence.MeshLocalCenter.IsFinite || !evidence.RendererLossyScale.IsFinite || !evidence.RendererWorldPosition.IsFinite || !FishWarehouseGaragePhysicalPanelDefinition.TryGetPhysicalAxisExtents(evidence.MeshLocalExtents, evidence.RendererLossyScale, out var physicalAxisExtents)) { return false; } FishWarehouseGarageVector vector = new FishWarehouseGarageVector(evidence.MeshLocalCenter.X * evidence.RendererLossyScale.X, evidence.MeshLocalCenter.Y * evidence.RendererLossyScale.Y, evidence.MeshLocalCenter.Z * evidence.RendererLossyScale.Z); anchorLocalCenter = ToAnchorLocalPoint(Add(evidence.RendererWorldPosition, Rotate(normalized3, vector)), anchor, normalized); FishWarehouseGarageQuaternion selectedChildRotationRelativeToPrimary = Multiply(Inverse(normalized2), normalized3); if (!FishWarehouseGaragePhysicalPanelDefinition.TryDerivePhysicalNormal(new FishWarehouseGaragePanelAxisEvidence(fishWarehouseGarageQuaternion, selectedChildRotationRelativeToPrimary, physicalAxisExtents), out normal)) { return false; } planeSource = FishWarehouseGaragePanelPlaneSource.MeshCenterAndThinAxis; } if (!anchorLocalCenter.IsFinite || !normal.IsFinite || normal.Length <= 0f) { return false; } mapping = new FishWarehouseGaragePanelRuntimeMapping(safetyEnvelope, new FishWarehousePhysicalPanelPlane(anchorLocalCenter, normal), planeSource); return true; } public static FishWarehouseAnchorLocalBounds ToAnchorLocalBounds(FishWarehouseWorldBounds worldBounds, FishWarehouseGarageRuntimeAnchor anchor) { if (!TryNormalize(anchor.WorldRotation, out var normalized)) { return InvalidBounds(); } return ToAnchorLocalBounds(worldBounds, anchor, normalized); } private static FishWarehouseAnchorLocalBounds ToAnchorLocalBounds(FishWarehouseWorldBounds worldBounds, FishWarehouseGarageRuntimeAnchor anchor, FishWarehouseGarageQuaternion anchorRotation) { FishWarehouseGarageVector fishWarehouseGarageVector = new FishWarehouseGarageVector(worldBounds.Center.X - worldBounds.Extents.X, worldBounds.Center.Y - worldBounds.Extents.Y, worldBounds.Center.Z - worldBounds.Extents.Z); FishWarehouseGarageVector fishWarehouseGarageVector2 = new FishWarehouseGarageVector(worldBounds.Center.X + worldBounds.Extents.X, worldBounds.Center.Y + worldBounds.Extents.Y, worldBounds.Center.Z + worldBounds.Extents.Z); return FishWarehouseGarageOpeningGeometryDefinition.ToAnchorLocalBounds(new FishWarehouseGarageVector[8] { new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector2.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector2.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector2.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector2.Z) }, (FishWarehouseGarageVector point) => ToAnchorLocalPoint(point, anchor, anchorRotation)); } private static FishWarehouseGarageVector ToAnchorLocalPoint(FishWarehouseGarageVector worldPoint, FishWarehouseGarageRuntimeAnchor anchor, FishWarehouseGarageQuaternion anchorRotation) { return Rotate(Inverse(anchorRotation), new FishWarehouseGarageVector(worldPoint.X - anchor.WorldPosition.X, worldPoint.Y - anchor.WorldPosition.Y, worldPoint.Z - anchor.WorldPosition.Z)); } internal static bool TryNormalize(FishWarehouseGarageQuaternion value, out FishWarehouseGarageQuaternion normalized) { normalized = default(FishWarehouseGarageQuaternion); float length = value.Length; if (!value.IsFinite || !float.IsFinite(length) || length <= 0f) { return false; } normalized = new FishWarehouseGarageQuaternion(value.X / length, value.Y / length, value.Z / length, value.W / length); return true; } internal static FishWarehouseGarageQuaternion Multiply(FishWarehouseGarageQuaternion left, FishWarehouseGarageQuaternion right) { return new FishWarehouseGarageQuaternion(left.W * right.X + left.X * right.W + left.Y * right.Z - left.Z * right.Y, left.W * right.Y - left.X * right.Z + left.Y * right.W + left.Z * right.X, left.W * right.Z + left.X * right.Y - left.Y * right.X + left.Z * right.W, left.W * right.W - left.X * right.X - left.Y * right.Y - left.Z * right.Z); } internal static FishWarehouseGarageQuaternion Inverse(FishWarehouseGarageQuaternion value) { return new FishWarehouseGarageQuaternion(0f - value.X, 0f - value.Y, 0f - value.Z, value.W); } internal static FishWarehouseGarageVector Rotate(FishWarehouseGarageQuaternion rotation, FishWarehouseGarageVector vector) { FishWarehouseGarageQuaternion fishWarehouseGarageQuaternion = Multiply(Multiply(rotation, new FishWarehouseGarageQuaternion(vector.X, vector.Y, vector.Z, 0f)), Inverse(rotation)); return new FishWarehouseGarageVector(fishWarehouseGarageQuaternion.X, fishWarehouseGarageQuaternion.Y, fishWarehouseGarageQuaternion.Z); } internal static FishWarehouseGarageVector Add(FishWarehouseGarageVector left, FishWarehouseGarageVector right) { return new FishWarehouseGarageVector(left.X + right.X, left.Y + right.Y, left.Z + right.Z); } private static FishWarehouseAnchorLocalBounds InvalidBounds() { return new FishWarehouseAnchorLocalBounds(float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN); } } public readonly record struct FishWarehouseGarageOverlapBoxRequest(FishWarehouseGarageVector WorldCenter, FishWarehouseGarageVector HalfExtents, FishWarehouseGarageQuaternion Rotation); public readonly record struct FishWarehouseGarageOverlapBoxHit(string Identity, FishWarehouseAnchorLocalBounds AnchorLocalBounds); public interface IFishWarehouseGarageOverlapBoxQuery { IReadOnlyList OverlapBox(FishWarehouseGarageOverlapBoxRequest request); } public static class FishWarehouseGarageOverlapBoxRequestDefinition { public static bool TryCreate(FishWarehouseTransitionPrism prism, FishWarehouseGarageRuntimeAnchor anchor, out FishWarehouseGarageOverlapBoxRequest request) { request = default(FishWarehouseGarageOverlapBoxRequest); if (!FishWarehouseGarageOpeningClearanceDefinition.TryGetTraversableVolume(prism, out FishWarehouseTransitionPrism traversableVolume) || !anchor.WorldPosition.IsFinite || !FishWarehouseGarageRuntimeEvidenceMapper.TryNormalize(anchor.WorldRotation, out var normalized)) { return false; } FishWarehouseGarageVector vector = new FishWarehouseGarageVector((traversableVolume.MinimumX + traversableVolume.MaximumX) / 2f, (traversableVolume.MinimumY + traversableVolume.MaximumY) / 2f, (traversableVolume.MinimumZ + traversableVolume.MaximumZ) / 2f); request = new FishWarehouseGarageOverlapBoxRequest(HalfExtents: new FishWarehouseGarageVector((traversableVolume.MaximumX - traversableVolume.MinimumX) / 2f, (traversableVolume.MaximumY - traversableVolume.MinimumY) / 2f, (traversableVolume.MaximumZ - traversableVolume.MinimumZ) / 2f), WorldCenter: FishWarehouseGarageRuntimeEvidenceMapper.Add(anchor.WorldPosition, FishWarehouseGarageRuntimeEvidenceMapper.Rotate(normalized, vector)), Rotation: anchor.WorldRotation); if (request.WorldCenter.IsFinite && request.HalfExtents.IsFinite && request.HalfExtents.X > 0f && request.HalfExtents.Y > 0f) { return request.HalfExtents.Z > 0f; } return false; } } public static class FishWarehouseGarageOverlapBoxClearanceDefinition { public static bool TryQueryNonTargetBounds(FishWarehouseTransitionPrism prism, FishWarehouseGarageRuntimeAnchor anchor, IFishWarehouseGarageOverlapBoxQuery? query, IEnumerable? targetColliderIds, out IReadOnlyList nonTargetBounds) { nonTargetBounds = Array.Empty(); if (query == null || targetColliderIds == null || !FishWarehouseGarageOverlapBoxRequestDefinition.TryCreate(prism, anchor, out var request)) { return false; } IReadOnlyList readOnlyList = query.OverlapBox(request); if (readOnlyList == null || readOnlyList.Any((FishWarehouseGarageOverlapBoxHit hit) => string.IsNullOrWhiteSpace(hit.Identity))) { return false; } HashSet targets = targetColliderIds.ToHashSet(StringComparer.Ordinal); FishWarehouseAnchorLocalBounds[] array = (from hit in readOnlyList where !targets.Contains(hit.Identity) select hit.AnchorLocalBounds).ToArray(); if (array.Any((FishWarehouseAnchorLocalBounds bound) => !bound.IsValid)) { return false; } nonTargetBounds = array; return true; } } public readonly record struct FishWarehousePhysicalPanelPlane(FishWarehouseGarageVector AnchorLocalCenter, FishWarehouseGarageVector AnchorLocalNormal); public static class FishWarehouseGarageOpeningGeometryDefinition { private const float MinimumNorthNormalAlignment = 0.98f; private const float MaximumVerticalNormalAlignment = 0.02f; private const float PlaneEnvelopeCenterTolerance = 0.25f; private const float AlternatePlaneCenterTolerance = 0.25f; private const float AlternateApertureEdgeTolerance = 0.25f; private const float MinimumAlternateNormalParallelism = 0.995f; public static FishWarehouseAnchorLocalBounds ToAnchorLocalBounds(FishWarehouseWorldBounds worldBounds, FishWarehouseAnchorFrame anchor) { if (!worldBounds.IsValid || !anchor.Position.IsFinite || !float.IsFinite(anchor.yawDegrees)) { return InvalidBounds(); } FishWarehouseGarageVector fishWarehouseGarageVector = new FishWarehouseGarageVector(worldBounds.Center.X - worldBounds.Extents.X, worldBounds.Center.Y - worldBounds.Extents.Y, worldBounds.Center.Z - worldBounds.Extents.Z); FishWarehouseGarageVector fishWarehouseGarageVector2 = new FishWarehouseGarageVector(worldBounds.Center.X + worldBounds.Extents.X, worldBounds.Center.Y + worldBounds.Extents.Y, worldBounds.Center.Z + worldBounds.Extents.Z); return ToAnchorLocalBounds(new FishWarehouseGarageVector[8] { new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector2.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector2.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector.Y, fishWarehouseGarageVector2.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector.Z), new FishWarehouseGarageVector(fishWarehouseGarageVector2.X, fishWarehouseGarageVector2.Y, fishWarehouseGarageVector2.Z) }, (FishWarehouseGarageVector point) => ToAnchorLocalPoint(point, anchor)); } public static FishWarehouseAnchorLocalBounds ToAnchorLocalBounds(IEnumerable? worldCorners, Func? toAnchorLocalPoint) { if (worldCorners == null || toAnchorLocalPoint == null) { return InvalidBounds(); } bool flag = false; float num = 0f; float num2 = 0f; float num3 = 0f; float num4 = 0f; float num5 = 0f; float num6 = 0f; foreach (FishWarehouseGarageVector worldCorner in worldCorners) { FishWarehouseGarageVector fishWarehouseGarageVector = toAnchorLocalPoint(worldCorner); if (!fishWarehouseGarageVector.IsFinite) { return InvalidBounds(); } if (!flag) { num = (num2 = fishWarehouseGarageVector.X); num3 = (num4 = fishWarehouseGarageVector.Y); num5 = (num6 = fishWarehouseGarageVector.Z); flag = true; } else { num = MathF.Min(num, fishWarehouseGarageVector.X); num2 = MathF.Max(num2, fishWarehouseGarageVector.X); num3 = MathF.Min(num3, fishWarehouseGarageVector.Y); num4 = MathF.Max(num4, fishWarehouseGarageVector.Y); num5 = MathF.Min(num5, fishWarehouseGarageVector.Z); num6 = MathF.Max(num6, fishWarehouseGarageVector.Z); } } if (!flag) { return InvalidBounds(); } return new FishWarehouseAnchorLocalBounds(num, num2, num3, num4, num5, num6); } public static FishWarehouseGarageVector ToAnchorLocalPoint(FishWarehouseGarageVector worldPoint, FishWarehouseAnchorFrame anchor) { return ToAnchorLocalDirection(new FishWarehouseGarageVector(worldPoint.X - anchor.Position.X, worldPoint.Y - anchor.Position.Y, worldPoint.Z - anchor.Position.Z), anchor); } public static FishWarehouseGarageVector ToAnchorLocalDirection(FishWarehouseGarageVector worldDirection, FishWarehouseAnchorFrame anchor) { float x = (0f - anchor.yawDegrees) * (float)Math.PI / 180f; float num = MathF.Cos(x); float num2 = MathF.Sin(x); return new FishWarehouseGarageVector(num * worldDirection.X + num2 * worldDirection.Z, worldDirection.Y, (0f - num2) * worldDirection.X + num * worldDirection.Z); } public static bool IsNorthWallPhysicalPlane(FishWarehouseAnchorLocalBounds safetyEnvelope, FishWarehousePhysicalPanelPlane plane) { if (!safetyEnvelope.IsValid || !plane.AnchorLocalCenter.IsFinite || !plane.AnchorLocalNormal.IsFinite) { return false; } float num = (safetyEnvelope.MinimumX + safetyEnvelope.MaximumX) / 2f; float num2 = (safetyEnvelope.MinimumZ + safetyEnvelope.MaximumZ) / 2f; if (MathF.Abs(plane.AnchorLocalCenter.X - num) > 0.25f || MathF.Abs(plane.AnchorLocalCenter.Z - num2) > 0.25f) { return false; } float length = plane.AnchorLocalNormal.Length; if (float.IsFinite(length) && length > 0f && MathF.Abs(plane.AnchorLocalNormal.Y / length) <= 0.02f) { return MathF.Abs(plane.AnchorLocalNormal.Z / length) >= 0.98f; } return false; } public static bool AreCoincidentPhysicalFaces(FishWarehouseAnchorLocalBounds primaryEnvelope, FishWarehousePhysicalPanelPlane primaryPlane, FishWarehouseAnchorLocalBounds alternateEnvelope, FishWarehousePhysicalPanelPlane alternatePlane) { if (!IsNorthWallPhysicalPlane(primaryEnvelope, primaryPlane) || !IsNorthWallPhysicalPlane(alternateEnvelope, alternatePlane)) { return false; } float length = primaryPlane.AnchorLocalNormal.Length; float length2 = alternatePlane.AnchorLocalNormal.Length; float num = (primaryPlane.AnchorLocalNormal.X * alternatePlane.AnchorLocalNormal.X + primaryPlane.AnchorLocalNormal.Y * alternatePlane.AnchorLocalNormal.Y + primaryPlane.AnchorLocalNormal.Z * alternatePlane.AnchorLocalNormal.Z) / (length * length2); if (!float.IsFinite(num) || MathF.Abs(num) < 0.995f) { return false; } FishWarehouseGarageVector fishWarehouseGarageVector = new FishWarehouseGarageVector(alternatePlane.AnchorLocalCenter.X - primaryPlane.AnchorLocalCenter.X, alternatePlane.AnchorLocalCenter.Y - primaryPlane.AnchorLocalCenter.Y, alternatePlane.AnchorLocalCenter.Z - primaryPlane.AnchorLocalCenter.Z); float num2 = MathF.Abs((fishWarehouseGarageVector.X * primaryPlane.AnchorLocalNormal.X + fishWarehouseGarageVector.Y * primaryPlane.AnchorLocalNormal.Y + fishWarehouseGarageVector.Z * primaryPlane.AnchorLocalNormal.Z) / length); if (float.IsFinite(num2) && num2 <= 0.25f && alternateEnvelope.MinimumX <= primaryEnvelope.MinimumX + 0.25f && alternateEnvelope.MaximumX >= primaryEnvelope.MaximumX - 0.25f && alternateEnvelope.MinimumY <= primaryEnvelope.MinimumY + 0.25f) { return alternateEnvelope.MaximumY >= primaryEnvelope.MaximumY - 0.25f; } return false; } private static FishWarehouseAnchorLocalBounds InvalidBounds() { return new FishWarehouseAnchorLocalBounds(float.NaN, float.NaN, float.NaN, float.NaN, float.NaN, float.NaN); } } public static class FishWarehouseGarageOpeningClearanceDefinition { public const float PedestrianVolumeFloorEpsilon = 0.01f; private const float BlockingSillTop = 0.85f; public static bool HasContinuousFloorSupport(IEnumerable>? rayHitLocalYs, float floorYTolerance) { if (rayHitLocalYs == null || !float.IsFinite(floorYTolerance) || floorYTolerance < 0f) { return false; } IReadOnlyList[] array = rayHitLocalYs.ToArray(); if (array.Length == 9) { return array.All((IReadOnlyList sample) => IsFloorSupportedAt(sample, floorYTolerance)); } return false; } public static bool IsFloorSupportedAt(IEnumerable? hitLocalYs, float floorYTolerance) { if (hitLocalYs != null && float.IsFinite(floorYTolerance) && floorYTolerance >= 0f) { return hitLocalYs.Any((float hitY) => float.IsFinite(hitY) && MathF.Abs(hitY - 0f) <= floorYTolerance); } return false; } public static bool TryGetTraversableVolume(FishWarehouseTransitionPrism prism, out FishWarehouseTransitionPrism traversableVolume) { traversableVolume = null; if (!AreFinite(prism.MinimumX, prism.MaximumX, prism.MinimumY, prism.MaximumY, prism.MinimumZ, prism.MaximumZ)) { return false; } float num = MathF.Max(prism.MinimumY, 0.01f); if (prism.MinimumX >= prism.MaximumX || num >= prism.MaximumY || prism.MinimumZ >= prism.MaximumZ) { return false; } traversableVolume = new FishWarehouseTransitionPrism(prism.MinimumX, prism.MaximumX, num, prism.MaximumY, prism.MinimumZ, prism.MaximumZ); return true; } public static bool IsTraversableVolumeClear(FishWarehouseTransitionPrism prism, IEnumerable? colliders) { if (colliders == null || !TryGetTraversableVolume(prism, out FishWarehouseTransitionPrism traversableVolume)) { return false; } FishWarehouseAnchorLocalBounds volumeBounds = ToBounds(traversableVolume); return colliders.All((FishWarehouseAnchorLocalBounds collider) => collider.IsValid && !collider.Intersects(volumeBounds)); } public static bool IsFloorBandClear(FishWarehouseTransitionPrism prism, IEnumerable? colliders) { if (colliders == null || !TryGetTraversableVolume(prism, out FishWarehouseTransitionPrism traversableVolume)) { return false; } float num = MathF.Min(traversableVolume.MaximumY, 0.85f); if (traversableVolume.MinimumY >= num) { return false; } FishWarehouseAnchorLocalBounds floorBand = new FishWarehouseAnchorLocalBounds(traversableVolume.MinimumX, traversableVolume.MaximumX, traversableVolume.MinimumY, num, traversableVolume.MinimumZ, traversableVolume.MaximumZ); return colliders.All((FishWarehouseAnchorLocalBounds collider) => collider.IsValid && !collider.Intersects(floorBand)); } private static FishWarehouseAnchorLocalBounds ToBounds(FishWarehouseTransitionPrism prism) { return new FishWarehouseAnchorLocalBounds(prism.MinimumX, prism.MaximumX, prism.MinimumY, prism.MaximumY, prism.MinimumZ, prism.MaximumZ); } private static bool AreFinite(params float[] values) { return values.All(float.IsFinite); } } public sealed record FishWarehouseGarageSceneNode(string HierarchyPath, string Name, string? ParentPath); public sealed record FishWarehouseGarageSceneSelection(string PrimaryRootPath, string PrimaryPanelPath, string AlternateRootPath); public static class FishWarehouseGarageSceneSelectionDefinition { public static bool TrySelect(IEnumerable? sceneNodes, out FishWarehouseGarageSceneSelection? selection) { selection = null; if (sceneNodes == null) { return false; } FishWarehouseGarageSceneNode[] source = sceneNodes.ToArray(); FishWarehouseGarageSceneNode[] primaryRoots = source.Where((FishWarehouseGarageSceneNode node) => string.Equals(node.HierarchyPath, "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse/GarageDoor (1)", StringComparison.Ordinal)).ToArray(); FishWarehouseGarageSceneNode[] array = source.Where((FishWarehouseGarageSceneNode node) => string.Equals(node.HierarchyPath, "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse/GarageDoor_Other", StringComparison.Ordinal)).ToArray(); if (primaryRoots.Length != 1 || array.Length != 1 || !string.Equals(primaryRoots[0].Name, "GarageDoor (1)", StringComparison.Ordinal) || !string.Equals(array[0].Name, "GarageDoor_Other", StringComparison.Ordinal)) { return false; } FishWarehouseGarageSceneNode[] array2 = source.Where((FishWarehouseGarageSceneNode node) => string.Equals(node.ParentPath, primaryRoots[0].HierarchyPath, StringComparison.Ordinal) && string.Equals(node.Name, primaryRoots[0].Name, StringComparison.Ordinal)).ToArray(); if (array2.Length != 1) { return false; } selection = new FishWarehouseGarageSceneSelection(primaryRoots[0].HierarchyPath, array2[0].HierarchyPath, array[0].HierarchyPath); return true; } } public enum FishWarehouseGarageTargetComponentKind { Renderer, Collider } public sealed record FishWarehouseGarageTargetComponent(string NodePath, string Identity, FishWarehouseGarageTargetComponentKind Kind, bool Enabled); public sealed record FishWarehouseGarageTargetSet(IReadOnlyList Renderers, IReadOnlyList Colliders) { public IReadOnlyList All => Renderers.Concat(Colliders).ToArray(); } public static class FishWarehouseGarageTargetSetDefinition { public static bool TryCreate(FishWarehouseGarageSceneSelection? selection, IEnumerable? components, out FishWarehouseGarageTargetSet? targetSet) { targetSet = null; if ((object)selection == null || components == null) { return false; } FishWarehouseGarageTargetComponent[] array = components.Where((FishWarehouseGarageTargetComponent component) => IsApprovedNode(component.NodePath, selection)).ToArray(); if (array.Any((FishWarehouseGarageTargetComponent component) => string.IsNullOrWhiteSpace(component.Identity)) || array.Select((FishWarehouseGarageTargetComponent component) => component.Identity).Distinct(StringComparer.Ordinal).Count() != array.Length) { return false; } FishWarehouseGarageTargetComponent[] array2 = Find(array, selection.PrimaryRootPath, FishWarehouseGarageTargetComponentKind.Renderer); FishWarehouseGarageTargetComponent[] array3 = Find(array, selection.PrimaryRootPath, FishWarehouseGarageTargetComponentKind.Collider); FishWarehouseGarageTargetComponent[] array4 = Find(array, selection.PrimaryPanelPath, FishWarehouseGarageTargetComponentKind.Renderer); FishWarehouseGarageTargetComponent[] array5 = Find(array, selection.PrimaryPanelPath, FishWarehouseGarageTargetComponentKind.Collider); FishWarehouseGarageTargetComponent[] array6 = Find(array, selection.AlternateRootPath, FishWarehouseGarageTargetComponentKind.Renderer); FishWarehouseGarageTargetComponent[] array7 = Find(array, selection.AlternateRootPath, FishWarehouseGarageTargetComponentKind.Collider); if (array2.Length != 1 || array3.Length != 1 || array4.Length != 1 || array5.Length != 1 || array6.Length != 1 || array7.Length != 0) { return false; } targetSet = new FishWarehouseGarageTargetSet(Array.AsReadOnly(array2.Concat(array4).Concat(array6).ToArray()), Array.AsReadOnly(array3.Concat(array5).ToArray())); return true; } private static bool IsApprovedNode(string nodePath, FishWarehouseGarageSceneSelection selection) { if (!string.Equals(nodePath, selection.PrimaryRootPath, StringComparison.Ordinal) && !string.Equals(nodePath, selection.PrimaryPanelPath, StringComparison.Ordinal)) { return string.Equals(nodePath, selection.AlternateRootPath, StringComparison.Ordinal); } return true; } private static FishWarehouseGarageTargetComponent[] Find(IEnumerable components, string nodePath, FishWarehouseGarageTargetComponentKind kind) { return components.Where((FishWarehouseGarageTargetComponent component) => string.Equals(component.NodePath, nodePath, StringComparison.Ordinal) && component.Kind == kind).ToArray(); } } public interface IFishWarehouseGarageTargetState { bool TryGetEnabled(FishWarehouseGarageTargetComponent component, out bool enabled); bool TrySetEnabled(FishWarehouseGarageTargetComponent component, bool enabled); } public sealed class FishWarehouseGarageTargetMutationState { private readonly FishWarehouseGarageTargetSet _targets; private IReadOnlyList? _snapshot; private bool _opened; public FishWarehouseGarageTargetMutationState(FishWarehouseGarageTargetSet targets) { _targets = targets ?? throw new ArgumentNullException("targets"); } public bool TryOpen(bool preflightPassed, IFishWarehouseGarageTargetState state, Func postOpenPrismIsClear) { if (!preflightPassed || state == null || postOpenPrismIsClear == null) { return false; } if (_opened) { return true; } if (!TryCapture(state)) { return false; } foreach (FishWarehouseGarageTargetComponent item in _targets.All) { if (!state.TrySetEnabled(item, enabled: false)) { Restore(state); return false; } } if (!postOpenPrismIsClear()) { Restore(state); return false; } _opened = true; return true; } public bool Restore(IFishWarehouseGarageTargetState state) { if (state == null) { return false; } if (_snapshot == null) { _opened = false; return true; } bool flag = true; foreach (FishWarehouseGarageTargetComponent item in _snapshot) { flag &= state.TrySetEnabled(item, item.Enabled); } if (flag) { _snapshot = null; } _opened = false; return flag; } private bool TryCapture(IFishWarehouseGarageTargetState state) { List list = new List(); foreach (FishWarehouseGarageTargetComponent item in _targets.All) { if (!state.TryGetEnabled(item, out var enabled)) { return false; } list.Add(item with { Enabled = enabled }); } _snapshot = list.AsReadOnly(); return true; } } public enum FishWarehouseClosurePlane { CeilingXZ, WallX, WallZ } public sealed record FishWarehouseClosurePiece(string SemanticId, FishWarehouseClosurePlane Plane, float PlaneCoordinate, float HorizontalMinimum, float HorizontalMaximum, float VerticalMinimum, float VerticalMaximum, bool DoubleSided) { public float Width => HorizontalMaximum - HorizontalMinimum; public float Height => VerticalMaximum - VerticalMinimum; public bool HasArea { get { if (Width > 0.001f) { return Height > 0.001f; } return false; } } } public static class FishWarehouseInteriorClosureDefinition { public const float CeilingHalfWidth = 12.2f; public const float CeilingHalfDepth = 7.6f; public const float GarageHeaderTopY = 6.8f; public const float PlaneInset = 0.05f; public static bool TryPlanCeiling(out FishWarehouseClosurePiece? ceiling) { ceiling = new FishWarehouseClosurePiece("ceiling", FishWarehouseClosurePlane.CeilingXZ, 5.8f, -12.2f, 12.2f, -7.6f, 7.6f, DoubleSided: false); return ceiling.HasArea; } public static bool TryPlanPersonnelSeal(FishWarehouseWallOpening? personnelOpening, out FishWarehouseClosurePiece? seal) { seal = null; if ((object)personnelOpening == null) { return false; } float bottom = personnelOpening.Bottom; float top = personnelOpening.Top; if (top - bottom <= 0.001f || personnelOpening.Width <= 0.001f) { return false; } switch (personnelOpening.Wall) { case FishWarehouseInteriorDoorSide.West: seal = BuildSeal(FishWarehouseClosurePlane.WallX, -11.6f, personnelOpening, bottom, top); break; case FishWarehouseInteriorDoorSide.East: seal = BuildSeal(FishWarehouseClosurePlane.WallX, 11.6f, personnelOpening, bottom, top); break; case FishWarehouseInteriorDoorSide.North: seal = BuildSeal(FishWarehouseClosurePlane.WallZ, 6.9f, personnelOpening, bottom, top); break; default: seal = BuildSeal(FishWarehouseClosurePlane.WallZ, -6.9f, personnelOpening, bottom, top); break; } return seal.HasArea; } private static FishWarehouseClosurePiece BuildSeal(FishWarehouseClosurePlane plane, float planeCoordinate, FishWarehouseWallOpening opening, float bottom, float top) { return new FishWarehouseClosurePiece("personnel-seal", plane, planeCoordinate, opening.HorizontalMinimum, opening.HorizontalMaximum, bottom, top, DoubleSided: true); } public static bool TryPlanGarageHeader(FishWarehouseGarageOpeningProjection projection, out FishWarehouseClosurePiece? header) { header = null; if ((object)projection == null) { return false; } float num = 6.8f; float top = projection.Opening.Top; if (num - top <= 0.001f) { return false; } float planeCoordinate = InsetTowardOrigin(projection.TransitionPrism.MaximumZ); header = new FishWarehouseClosurePiece("garage-header", FishWarehouseClosurePlane.WallZ, planeCoordinate, projection.Opening.HorizontalMinimum, projection.Opening.HorizontalMaximum, top, num, DoubleSided: true); if (header.HasArea) { return DoesNotIntrudeIntoGarageAperture(header, projection); } return false; } private static float InsetTowardOrigin(float planeCoordinate) { if (!(planeCoordinate > 0f)) { return planeCoordinate + 0.05f; } return planeCoordinate - 0.05f; } private static bool DoesNotIntrudeIntoGarageAperture(FishWarehouseClosurePiece header, FishWarehouseGarageOpeningProjection projection) { return header.VerticalMinimum >= projection.Opening.Top - 0.001f; } } public enum FishWarehouseInteriorDoorSide { West, East, South, North } public static class FishWarehouseInteriorRoomDefinition { public const float HalfWidth = 11.6f; public const float HalfDepth = 6.9f; public const float FloorY = 0f; public const float CeilingY = 5.8f; public const float DoorWidth = 2.2f; public const float DoorHeight = 3f; public const float WallInset = 0.6f; public static FishWarehouseInteriorDoorSide ResolveDoorSide(float localX, float localZ) { float num = MathF.Abs(localX + 11.6f); float num2 = MathF.Abs(11.6f - localX); float num3 = MathF.Abs(localZ + 6.9f); float y = MathF.Abs(6.9f - localZ); float num4 = MathF.Min(MathF.Min(num, num2), MathF.Min(num3, y)); if (num4 == num) { return FishWarehouseInteriorDoorSide.West; } if (num4 == num2) { return FishWarehouseInteriorDoorSide.East; } if (num4 == num3) { return FishWarehouseInteriorDoorSide.South; } return FishWarehouseInteriorDoorSide.North; } public static bool TryPlanRoomWalls(float personnelLocalX, float personnelLocalZ, FishWarehouseWallOpening garageOpening, out IReadOnlyList? openings, out IReadOnlyList? wallPlans) { openings = null; wallPlans = null; if (!FishWarehouseWallOpeningPlanner.TryCreatePersonnelOpening(personnelLocalX, personnelLocalZ, out FishWarehouseWallOpening opening) || (object)opening == null) { return false; } FishWarehouseWallOpening[] array = new FishWarehouseWallOpening[2] { opening, garageOpening }; List list = new List(); FishWarehouseInteriorDoorSide[] values = Enum.GetValues(); foreach (FishWarehouseInteriorDoorSide wall in values) { if (!FishWarehouseWallOpeningPlanner.TryPlan(array.Where((FishWarehouseWallOpening fishWarehouseWallOpening) => fishWarehouseWallOpening.Wall == wall).ToArray(), out FishWarehouseWallPlan plan, wall) || (object)plan == null) { return false; } list.Add(plan); } openings = Array.AsReadOnly(array); wallPlans = list.AsReadOnly(); return true; } } public static class FishWarehouseInteriorShellDefinition { public const string BuildingPathToken = "Map/Hyland Point/Region_Docks/Fish Warehouse"; public static bool IsFishWarehouseSourcePath(string? hierarchyPath) { if (!string.IsNullOrWhiteSpace(hierarchyPath)) { return hierarchyPath.Contains("Map/Hyland Point/Region_Docks/Fish Warehouse", StringComparison.OrdinalIgnoreCase); } return false; } public static bool IsRendererCloneCandidate(string componentTypeName, bool hasMesh) { if (hasMesh) { if (!string.Equals(componentTypeName, "UnityEngine.Renderer", StringComparison.Ordinal)) { return string.Equals(componentTypeName, "UnityEngine.MeshRenderer", StringComparison.Ordinal); } return true; } return false; } public static bool CanPrepareMaterialSlots(int slotCount, int usableSlotCount) { if (slotCount > 0) { return usableSlotCount > 0; } return false; } public static bool RequiresMaterialFallback(IEnumerable? supportedProperties) { if (supportedProperties != null) { if (!supportedProperties.Contains("_Cull", StringComparer.Ordinal)) { return !supportedProperties.Contains("_CullMode", StringComparer.Ordinal); } return false; } return true; } } public sealed record FishWarehouseDockDefinition(int Index, string Name, string DockGuid, string ParkingLotGuid, float CenterX, float CenterY, float CenterZ, float YawDegrees, float HalfWidth, float HalfLength); public sealed record FishWarehouseDockOccupancySnapshot(bool HasDynamicVehicle, bool HasStaticVehicle, bool HasActiveDelivery); public sealed record FishWarehouseDetectorColliderBindingDiagnostics(int RuntimeReferenceCount, int HierarchyColliderCount, int OwnedBoxColliderCount); public static class FishWarehouseLoadingDockDefinition { public const string TargetPropertyCode = "oc_fishwarehouse"; public const string SourcePropertyCode = "barn"; public const float FrontageMinX = 4.1f; public const float FrontageMaxX = 9.55f; public const float FrontageMinZ = 7.55f; public const float FrontageMaxZ = 13.95f; public const float DetectorHeight = 2.8f; public static IReadOnlyList Docks { get; } = Array.AsReadOnly(new FishWarehouseDockDefinition[2] { new FishWarehouseDockDefinition(0, "OC Fish Warehouse Dock 1", "978f8d8e-e2bc-42fd-b218-3aa34cd4fb31", "b917f58c-6162-4586-940b-1ddbcc3cc559", 6.82f, 0f, 12.285f, 90f, 1.5f, 2.6f), new FishWarehouseDockDefinition(1, "OC Fish Warehouse Dock 2", "520a20f6-aa83-4458-83bc-9106dcbd7a0c", "290a4590-136f-44f2-8df4-729548dd1940", 6.82f, 0f, 9.195f, 90f, 1.5f, 2.6f) }); public static (float X, float Z) GetAxisAlignedHalfExtents(FishWarehouseDockDefinition dock) { ArgumentNullException.ThrowIfNull(dock, "dock"); float x = dock.YawDegrees * ((float)Math.PI / 180f); float num = MathF.Abs(MathF.Cos(x)); float num2 = MathF.Abs(MathF.Sin(x)); return (X: num * dock.HalfWidth + num2 * dock.HalfLength, Z: num2 * dock.HalfWidth + num * dock.HalfLength); } public static bool IsInsideFrontageEnvelope(FishWarehouseDockDefinition dock) { if (dock.CenterX - GetAxisAlignedHalfExtents(dock).X >= 4.1f && dock.CenterX + GetAxisAlignedHalfExtents(dock).X <= 9.55f && dock.CenterZ - GetAxisAlignedHalfExtents(dock).Z >= 7.55f) { return dock.CenterZ + GetAxisAlignedHalfExtents(dock).Z <= 13.95f; } return false; } public static bool Overlaps(FishWarehouseDockDefinition left, FishWarehouseDockDefinition right) { (float, float) axisAlignedHalfExtents = GetAxisAlignedHalfExtents(left); (float, float) axisAlignedHalfExtents2 = GetAxisAlignedHalfExtents(right); if (MathF.Abs(left.CenterX - right.CenterX) < axisAlignedHalfExtents.Item1 + axisAlignedHalfExtents2.Item1) { return MathF.Abs(left.CenterZ - right.CenterZ) < axisAlignedHalfExtents.Item2 + axisAlignedHalfExtents2.Item2; } return false; } public static bool HasValidLayout() { if (Docks.Count != 2 || !Docks.Select((FishWarehouseDockDefinition dock) => dock.Index).SequenceEqual(new int[2] { 0, 1 })) { return false; } string[] array = Docks.SelectMany((FishWarehouseDockDefinition dock) => new string[2] { dock.DockGuid, dock.ParkingLotGuid }).ToArray(); if (array.All((string identity) => Guid.TryParse(identity, out var _)) && array.Distinct(StringComparer.OrdinalIgnoreCase).Count() == array.Length && Docks.All(IsInsideFrontageEnvelope)) { return !Overlaps(Docks[0], Docks[1]); } return false; } public static bool CanPublish(int stagedCount, int validCount) { if (stagedCount == Docks.Count) { return validCount == Docks.Count; } return false; } public static bool TryBindDetectorCollider(IReadOnlyCollection runtimeReferences, IReadOnlyCollection hierarchyColliders, Func tryCastBoxCollider, Func isCloneOwned, out TBoxCollider? selected, out FishWarehouseDetectorColliderBindingDiagnostics diagnostics) where TCollider : class where TBoxCollider : class { ArgumentNullException.ThrowIfNull(runtimeReferences, "runtimeReferences"); ArgumentNullException.ThrowIfNull(hierarchyColliders, "hierarchyColliders"); ArgumentNullException.ThrowIfNull(tryCastBoxCollider, "tryCastBoxCollider"); ArgumentNullException.ThrowIfNull(isCloneOwned, "isCloneOwned"); TBoxCollider[] array = (from collider in hierarchyColliders.Where(isCloneOwned).Select(tryCastBoxCollider) where collider != null select collider).Cast().ToArray(); diagnostics = new FishWarehouseDetectorColliderBindingDiagnostics(runtimeReferences.Count, hierarchyColliders.Count, array.Length); if (array.Length != 1) { selected = null; return false; } selected = array[0]; return true; } public static bool CanUnload(IEnumerable occupancy) { return occupancy.All((FishWarehouseDockOccupancySnapshot snapshot) => !snapshot.HasDynamicVehicle && !snapshot.HasStaticVehicle && !snapshot.HasActiveDelivery); } } public enum FishWarehouseNativeNavigationGraphRole { Default, Employee } public enum FishWarehouseNativeNavigationPathStatus { PathComplete, PathPartial, PathInvalid } public readonly record struct FishWarehouseNativeNavigationProbe(FishWarehouseNativeNavigationGraphRole GraphRole, int AgentTypeId, string TargetName, bool Required, bool TargetPresent, bool StartSampled, bool EndSampled, FishWarehouseNativeNavigationPathStatus PathStatus); public readonly record struct FishWarehouseNativeNavigationAcceptedRequirement(int AgentTypeId, string TargetName, FishWarehouseNativeNavigationGraphRole GraphRole); public sealed record FishWarehouseNativeNavigationAcceptanceResult(bool Succeeded, IReadOnlyList Warnings, IReadOnlyList Failures, IReadOnlyList AcceptedRequirements) { public int RequiredResourceCount => AcceptedRequirements.Count; } public static class FishWarehouseNativeNavigationAcceptanceDefinition { public const string EmployeeExteriorToDoorwayTargetName = "employee-exterior-to-doorway"; public const string DefaultExteriorToDoorwayTargetName = "default-exterior-to-doorway"; private static IReadOnlyList<(FishWarehouseNativeNavigationGraphRole GraphRole, string TargetName)> RequiredPathInventory { get; } = CreateRequiredPathInventory(); public static string DoorwayToIdleTargetName(int index) { if (index < 0 || index >= FishWarehouseEmployeeInfrastructureDefinition.IdlePoints.Count) { throw new ArgumentOutOfRangeException("index", index, $"The Fish Warehouse has {FishWarehouseEmployeeInfrastructureDefinition.IdlePoints.Count} mandatory idle points."); } return $"doorway-to-idle-{index}"; } private static IReadOnlyList<(FishWarehouseNativeNavigationGraphRole GraphRole, string TargetName)> CreateRequiredPathInventory() { List<(FishWarehouseNativeNavigationGraphRole, string)> list = new List<(FishWarehouseNativeNavigationGraphRole, string)> { (FishWarehouseNativeNavigationGraphRole.Employee, "employee-exterior-to-doorway") }; for (int i = 0; i < FishWarehouseEmployeeInfrastructureDefinition.IdlePoints.Count; i++) { list.Add((FishWarehouseNativeNavigationGraphRole.Employee, DoorwayToIdleTargetName(i))); } list.Add((FishWarehouseNativeNavigationGraphRole.Default, "default-exterior-to-doorway")); return list; } public static FishWarehouseNativeNavigationAcceptanceResult Evaluate(IEnumerable probes) { ArgumentNullException.ThrowIfNull(probes, "probes"); List warnings = new List(); List list = new List(); HashSet hashSet = new HashSet(); HashSet<(FishWarehouseNativeNavigationGraphRole, string)> hashSet2 = new HashSet<(FishWarehouseNativeNavigationGraphRole, string)>(); foreach (FishWarehouseNativeNavigationProbe probe in probes) { if (probe.Required) { hashSet2.Add((probe.GraphRole, probe.TargetName)); } if (!probe.TargetPresent) { AddIssue(probe, "target=" + probe.TargetName + " is unavailable", warnings, list); continue; } if (!probe.StartSampled) { AddIssue(probe, "target=" + probe.TargetName + " start endpoint was not sampled", warnings, list); } if (!probe.EndSampled) { AddIssue(probe, "target=" + probe.TargetName + " end endpoint was not sampled", warnings, list); } if (probe.PathStatus != FishWarehouseNativeNavigationPathStatus.PathComplete) { AddIssue(probe, $"target={probe.TargetName} path status={probe.PathStatus}", warnings, list); } if (!probe.Required || !probe.StartSampled || !probe.EndSampled || probe.PathStatus != FishWarehouseNativeNavigationPathStatus.PathComplete) { continue; } FishWarehouseNativeNavigationAcceptedRequirement item = new FishWarehouseNativeNavigationAcceptedRequirement(probe.AgentTypeId, probe.TargetName, probe.GraphRole); if (probe.AgentTypeId == 0) { if (!hashSet.Any((FishWarehouseNativeNavigationAcceptedRequirement requirement) => requirement.AgentTypeId == 0 && requirement.TargetName == probe.TargetName)) { hashSet.Add(item); } } else { hashSet.Add(item); } } foreach (var (item2, text) in RequiredPathInventory) { if (!hashSet2.Contains((item2, text))) { list.Add("missing required probe graph=" + item2.ToString().ToLowerInvariant() + " target=" + text); } } return new FishWarehouseNativeNavigationAcceptanceResult(list.Count == 0, warnings, list, hashSet.OrderBy((FishWarehouseNativeNavigationAcceptedRequirement requirement) => requirement.AgentTypeId).ThenBy((FishWarehouseNativeNavigationAcceptedRequirement requirement) => requirement.TargetName, StringComparer.Ordinal).ToArray()); } private static void AddIssue(FishWarehouseNativeNavigationProbe probe, string message, ICollection warnings, ICollection failures) { string item = $"graph={probe.GraphRole.ToString().ToLowerInvariant()} required={probe.Required} {message}"; if (probe.Required) { failures.Add(item); } else { warnings.Add(item); } } } public readonly record struct FishWarehouseNativeNavigationVector(float X, float Y, float Z); public readonly record struct FishWarehouseNativeNavigationSurface(FishWarehouseNativeNavigationVector Center, FishWarehouseNativeNavigationVector Size, int Area, bool GenerateLinks); public readonly record struct FishWarehouseNativeNavigationBox(FishWarehouseNativeNavigationVector Center, FishWarehouseNativeNavigationVector Size); public readonly record struct FishWarehouseNativeNavigationLink(FishWarehouseNativeNavigationVector Start, FishWarehouseNativeNavigationVector NominalExteriorQueryPoint, float Width, float SideInset, bool Bidirectional); public sealed record FishWarehouseNativeNavigationPlan(FishWarehouseNativeNavigationSurface Surface, FishWarehouseNativeNavigationBox BakeBounds, FishWarehouseNativeNavigationLink Link, FishWarehouseNativeNavigationVector DoorwayInterior, float SampleRadius); public readonly record struct FishWarehouseNativeGraphProbe(bool ExteriorSampled, bool InteriorSampled, bool PathComplete); public enum FishWarehouseNativeGraphDecision { Fail, AddSurfaceAndLink, AddLinkOnly, ReuseExistingPath } public static class FishWarehouseNativeGraphDecisionDefinition { public static FishWarehouseNativeGraphDecision Decide(FishWarehouseNativeGraphProbe probe) { if (probe.ExteriorSampled) { if (probe.InteriorSampled) { if (probe.PathComplete) { return FishWarehouseNativeGraphDecision.ReuseExistingPath; } return FishWarehouseNativeGraphDecision.AddLinkOnly; } return FishWarehouseNativeGraphDecision.AddSurfaceAndLink; } return FishWarehouseNativeGraphDecision.Fail; } } public static class FishWarehouseNativeNavigationDefinition { public const float SurfaceThickness = 0.1f; public const float LinkSideInset = 0.35f; public const float MinimumLinkWidth = 1.2f; public const float BakeBoundsPadding = 0.5f; public const float SampleRadius = 1f; private const float TransitionClearance = 0.3f; private const float ExteriorSampleClearance = 2.2f; private const float ProjectionTolerance = 0.001f; private const float MaximumTransitionDepth = 1f; private const float MinimumProjectedLintel = 0.3f; public static FishWarehouseNativeNavigationPlan Create(FishWarehouseGarageOpeningProjection projection) { ArgumentNullException.ThrowIfNull(projection, "projection"); ValidateProjection(projection); float num = -6.9f; float maximumZ = projection.TransitionPrism.MaximumZ; float num2 = maximumZ - num; float num3 = 23.2f; FishWarehouseNativeNavigationSurface surface = new FishWarehouseNativeNavigationSurface(new FishWarehouseNativeNavigationVector(0f, -0.05f, (num + maximumZ) / 2f), new FishWarehouseNativeNavigationVector(num3, 0.1f, num2), 0, GenerateLinks: false); float num4 = -0.5f; float num5 = 6.3f; FishWarehouseNativeNavigationBox bakeBounds = new FishWarehouseNativeNavigationBox(new FishWarehouseNativeNavigationVector(0f, (num4 + num5) / 2f, (num + maximumZ) / 2f), new FishWarehouseNativeNavigationVector(num3 + 1f, num5 - num4, num2 + 1f)); float num6 = projection.NavigationBounds.Width - 0.7f; if (num6 < 1.2f) { throw new ArgumentOutOfRangeException("projection", num6, $"The inset native garage link must be at least {1.2f:0.00}m wide."); } float center = projection.Opening.Center; FishWarehouseNativeNavigationVector doorwayInterior = new FishWarehouseNativeNavigationVector(center, 0f, projection.TransitionPrism.MinimumZ - 0.3f); FishWarehouseNativeNavigationVector start = new FishWarehouseNativeNavigationVector(center, 0f, projection.TransitionPrism.MaximumZ - 0.3f); FishWarehouseNativeNavigationVector nominalExteriorQueryPoint = new FishWarehouseNativeNavigationVector(center, 0f, projection.TransitionPrism.MaximumZ + 2.2f); return new FishWarehouseNativeNavigationPlan(surface, bakeBounds, new FishWarehouseNativeNavigationLink(start, nominalExteriorQueryPoint, num6, 0.35f, Bidirectional: true), doorwayInterior, 1f); } private static void ValidateProjection(FishWarehouseGarageOpeningProjection projection) { ArgumentNullException.ThrowIfNull(projection.Opening, "projection.Opening"); ArgumentNullException.ThrowIfNull(projection.TransitionPrism, "projection.TransitionPrism"); ArgumentNullException.ThrowIfNull(projection.NavigationBounds, "projection.NavigationBounds"); if (projection.Opening.Wall != FishWarehouseInteriorDoorSide.North) { throw new ArgumentException("The native garage opening must be on the north wall.", "projection"); } if (!IsFinite(projection.RawMinimumX, projection.RawMaximumX, projection.Lintel, projection.Opening.HorizontalMinimum, projection.Opening.HorizontalMaximum, projection.Opening.Bottom, projection.Opening.Top, projection.TransitionPrism.MinimumX, projection.TransitionPrism.MaximumX, projection.TransitionPrism.MinimumY, projection.TransitionPrism.MaximumY, projection.TransitionPrism.MinimumZ, projection.TransitionPrism.MaximumZ, projection.NavigationBounds.MinimumX, projection.NavigationBounds.MaximumX)) { throw new ArgumentException("The native garage projection must contain only finite coordinates.", "projection"); } if (projection.Opening.HorizontalMinimum >= projection.Opening.HorizontalMaximum || projection.Opening.Bottom >= projection.Opening.Top || projection.RawMinimumX >= projection.RawMaximumX || projection.RawMinimumX > projection.Opening.HorizontalMinimum + 0.001f || projection.RawMaximumX < projection.Opening.HorizontalMaximum - 0.001f || projection.Opening.HorizontalMinimum < -11.6f || projection.Opening.HorizontalMaximum > 11.6f || projection.Opening.Bottom < 0f || projection.Opening.Top > 5.8f || projection.Lintel < 0.3f || MathF.Abs(projection.Lintel - (5.8f - projection.Opening.Top)) > 0.001f || MathF.Abs(projection.TransitionPrism.MinimumX - projection.Opening.HorizontalMinimum) > 0.001f || MathF.Abs(projection.TransitionPrism.MaximumX - projection.Opening.HorizontalMaximum) > 0.001f || MathF.Abs(projection.TransitionPrism.MinimumY - projection.Opening.Bottom) > 0.001f || MathF.Abs(projection.TransitionPrism.MaximumY - projection.Opening.Top) > 0.001f || projection.TransitionPrism.MinimumX >= projection.TransitionPrism.MaximumX || projection.TransitionPrism.MinimumY >= projection.TransitionPrism.MaximumY || projection.TransitionPrism.MinimumZ >= projection.TransitionPrism.MaximumZ || MathF.Abs(projection.TransitionPrism.MinimumZ - 6.9f) > 0.001f || projection.TransitionPrism.Depth > 1f || projection.NavigationBounds.MinimumX >= projection.NavigationBounds.MaximumX || projection.NavigationBounds.Width < 0.7f || projection.NavigationBounds.Width > projection.Opening.Width) { throw new ArgumentException("The native garage projection geometry is invalid.", "projection"); } float center = projection.Opening.Center; float num = projection.TransitionPrism.MinimumZ - 0.3f; float num2 = projection.TransitionPrism.MaximumZ - 0.3f; if (center <= -11.6f || center >= 11.6f || num <= -6.9f || num >= 6.9f || num2 <= projection.TransitionPrism.MinimumZ || num2 >= projection.TransitionPrism.MaximumZ) { throw new ArgumentException("The native garage projection cannot produce interior validation points.", "projection"); } } private static bool IsFinite(params float[] values) { return values.All(float.IsFinite); } } public enum FishWarehouseNativeNavigationTeardownOperationKind { RemoveLink, RemoveData, ClearNativeSampleCache } public readonly record struct FishWarehouseNativeNavigationTeardownOperation(FishWarehouseNativeNavigationTeardownOperationKind Kind, FishWarehouseNativeNavigationGraphRole GraphRole, int? ResourceKey) { public override string ToString() { return Kind switch { FishWarehouseNativeNavigationTeardownOperationKind.RemoveLink => "remove-link:" + GraphRole.ToString().ToLowerInvariant(), FishWarehouseNativeNavigationTeardownOperationKind.RemoveData => "remove-data:" + GraphRole.ToString().ToLowerInvariant(), FishWarehouseNativeNavigationTeardownOperationKind.ClearNativeSampleCache => "clear-native-sample-cache", _ => throw new ArgumentOutOfRangeException("Kind", Kind, null), }; } } public sealed class FishWarehouseNativeNavigationResourceLedger { private readonly record struct ResourceEntry(FishWarehouseNativeNavigationGraphRole GraphRole, int ResourceKey); private readonly HashSet _data = new HashSet(); private readonly HashSet _links = new HashSet(); private bool _cacheClearPending; public bool HasPendingTeardown => _cacheClearPending; public void RecordData(FishWarehouseNativeNavigationGraphRole graphRole, int resourceKey) { _data.Add(new ResourceEntry(graphRole, resourceKey)); _cacheClearPending = true; } public void RecordLink(FishWarehouseNativeNavigationGraphRole graphRole, int resourceKey) { _links.Add(new ResourceEntry(graphRole, resourceKey)); _cacheClearPending = true; } public IReadOnlyList PendingTeardown() { if (!_cacheClearPending) { return Array.Empty(); } List list = new List(_links.Count + _data.Count + 1); AddRemoveOperations(_links, FishWarehouseNativeNavigationTeardownOperationKind.RemoveLink, list); AddRemoveOperations(_data, FishWarehouseNativeNavigationTeardownOperationKind.RemoveData, list); list.Add(new FishWarehouseNativeNavigationTeardownOperation(FishWarehouseNativeNavigationTeardownOperationKind.ClearNativeSampleCache, FishWarehouseNativeNavigationGraphRole.Default, null)); return list; } public void Acknowledge(FishWarehouseNativeNavigationTeardownOperation operation) { switch (operation.Kind) { case FishWarehouseNativeNavigationTeardownOperationKind.RemoveLink: AcknowledgeResource(_links, operation); break; case FishWarehouseNativeNavigationTeardownOperationKind.RemoveData: AcknowledgeResource(_data, operation); break; case FishWarehouseNativeNavigationTeardownOperationKind.ClearNativeSampleCache: if (!_cacheClearPending || _links.Count != 0 || _data.Count != 0) { throw new InvalidOperationException("Fish Warehouse native navigation cache cannot be acknowledged before every native resource is removed."); } _cacheClearPending = false; break; default: throw new ArgumentOutOfRangeException("operation", operation.Kind, null); } } private static void AcknowledgeResource(ISet resources, FishWarehouseNativeNavigationTeardownOperation operation) { int? resourceKey = operation.ResourceKey; if (resourceKey.HasValue) { int valueOrDefault = resourceKey.GetValueOrDefault(); if (resources.Remove(new ResourceEntry(operation.GraphRole, valueOrDefault))) { return; } } throw new InvalidOperationException($"Fish Warehouse native navigation teardown acknowledgement did not match {operation}."); } private static void AddRemoveOperations(IEnumerable resources, FishWarehouseNativeNavigationTeardownOperationKind kind, ICollection operations) { foreach (ResourceEntry item in from resource in resources orderby resource.GraphRole, resource.ResourceKey select resource) { operations.Add(new FishWarehouseNativeNavigationTeardownOperation(kind, item.GraphRole, item.ResourceKey)); } } } public static class FishWarehouseOwnershipReconciler { public static bool IsOwnedByEvidence(bool sidecarOwned, int capturedObjectCount, int capturedEmployeeCount, bool? nativeIsOwned) { if (nativeIsOwned == true) { return true; } if (sidecarOwned) { return true; } if (capturedObjectCount <= 0) { return capturedEmployeeCount > 0; } return true; } } public readonly record struct FishWarehousePoint(float X, float Y, float Z); public static class FishWarehousePropertyBoundsDefinition { public const float MinX = -15.5f; public const float MaxX = 11.6f; public const float MinY = -0.5f; public const float MaxY = 6.3f; public const float MinZ = -6.9f; public const float MaxZ = 6.9f; public static bool Contains(float x, float y, float z) { if (x >= -15.5f && x <= 11.6f && y >= -0.5f && y <= 6.3f && z >= -6.9f) { return z <= 6.9f; } return false; } public static bool IsStagedAttachmentUsable(bool boundsRootActiveSelf, bool colliderEnabled) { return boundsRootActiveSelf && colliderEnabled; } } public readonly record struct FishWarehouseTransformVector(float X, float Y, float Z); public readonly record struct FishWarehouseTransformSignature(FishWarehouseTransformVector Position, FishWarehouseTransformVector EulerRotation, FishWarehouseTransformVector LossyScale); public static class FishWarehouseTransformSignatureDefinition { public const float PositionTolerance = 0.01f; public const float RotationToleranceDegrees = 0.1f; public const float UnitScaleTolerance = 0.001f; public static bool IsCoincident(FishWarehouseTransformSignature anchor, FishWarehouseTransformSignature propertyRoot) { if (ComponentsWithin(anchor.Position, propertyRoot.Position, 0.01f) && RotationWithin(anchor.EulerRotation, propertyRoot.EulerRotation, 0.1f) && IsUnitScale(anchor.LossyScale)) { return IsUnitScale(propertyRoot.LossyScale); } return false; } private static bool ComponentsWithin(FishWarehouseTransformVector left, FishWarehouseTransformVector right, float tolerance) { if (float.IsFinite(left.X) && float.IsFinite(left.Y) && float.IsFinite(left.Z) && float.IsFinite(right.X) && float.IsFinite(right.Y) && float.IsFinite(right.Z) && MathF.Abs(left.X - right.X) <= tolerance && MathF.Abs(left.Y - right.Y) <= tolerance) { return MathF.Abs(left.Z - right.Z) <= tolerance; } return false; } private static bool RotationWithin(FishWarehouseTransformVector left, FishWarehouseTransformVector right, float tolerance) { return ComponentsWithin(new FishWarehouseTransformVector(AngleDifference(left.X, right.X), AngleDifference(left.Y, right.Y), AngleDifference(left.Z, right.Z)), new FishWarehouseTransformVector(0f, 0f, 0f), tolerance); } private static bool IsUnitScale(FishWarehouseTransformVector scale) { if (float.IsFinite(scale.X) && float.IsFinite(scale.Y) && float.IsFinite(scale.Z) && MathF.Abs(scale.X - 1f) <= 0.001f && MathF.Abs(scale.Y - 1f) <= 0.001f) { return MathF.Abs(scale.Z - 1f) <= 0.001f; } return false; } private static float AngleDifference(float left, float right) { float num = (left - right) % 360f; if (num > 180f) { num -= 360f; } else if (num < -180f) { num += 360f; } return num; } } public sealed record FishWarehouseWallOpening(FishWarehouseInteriorDoorSide Wall, float HorizontalMinimum, float HorizontalMaximum, float Bottom, float Top, bool IsCornerTerminatedAtMinimum = false, bool IsCornerTerminatedAtMaximum = false, string? SemanticId = null) { public float Width => HorizontalMaximum - HorizontalMinimum; public float Height => Top - Bottom; public float Center => (HorizontalMinimum + HorizontalMaximum) / 2f; } public sealed record FishWarehouseWallSegment(FishWarehouseInteriorDoorSide Wall, float HorizontalMinimum, float HorizontalMaximum, float Bottom, float Top) { public float Width => HorizontalMaximum - HorizontalMinimum; public float Height => Top - Bottom; } public sealed record FishWarehouseWallPlan(FishWarehouseInteriorDoorSide Wall, IReadOnlyList Segments); public static class FishWarehouseWallOpeningPlanner { public const float Epsilon = 0.001f; private const float MinimumResidualWallMargin = 0.5f; private const float MinimumClippedPersonnelWidth = 1.2f; public static bool TryCreatePersonnelOpening(float localX, float localZ, out FishWarehouseWallOpening? opening) { opening = null; if (!float.IsFinite(localX) || !float.IsFinite(localZ)) { return false; } FishWarehouseInteriorDoorSide fishWarehouseInteriorDoorSide = FishWarehouseInteriorRoomDefinition.ResolveDoorSide(localX, localZ); bool flag = (uint)fishWarehouseInteriorDoorSide <= 1u; float num = (flag ? localZ : localX); (float Minimum, float Maximum) horizontalBounds = GetHorizontalBounds(fishWarehouseInteriorDoorSide); float item = horizontalBounds.Minimum; float item2 = horizontalBounds.Maximum; float num2 = MathF.Max(item, num - 1.1f); float num3 = MathF.Min(item2, num + 1.1f); if (num3 - num2 < 1.2f) { return false; } opening = new FishWarehouseWallOpening(fishWarehouseInteriorDoorSide, num2, num3, 0f, 3f, MathF.Abs(num2 - item) <= 0.001f, MathF.Abs(num3 - item2) <= 0.001f, "personnel"); return true; } public static bool TryPlan(IEnumerable? openings, out FishWarehouseWallPlan? plan) { return TryPlan(openings, out plan, null); } public static bool TryPlan(IEnumerable? openings, out FishWarehouseWallPlan? plan, FishWarehouseInteriorDoorSide wall) { return TryPlan(openings, out plan, (FishWarehouseInteriorDoorSide?)wall); } private static bool TryPlan(IEnumerable? openings, out FishWarehouseWallPlan? plan, FishWarehouseInteriorDoorSide? requestedWall) { plan = null; if (openings == null) { return false; } FishWarehouseWallOpening[] array = openings.ToArray(); FishWarehouseInteriorDoorSide wall = requestedWall ?? ((array.Length != 0) ? array[0].Wall : FishWarehouseInteriorDoorSide.North); if (array.Any((FishWarehouseWallOpening opening) => opening.Wall != wall)) { return false; } (float Minimum, float Maximum) horizontalBounds = GetHorizontalBounds(wall); float item = horizontalBounds.Minimum; float item2 = horizontalBounds.Maximum; FishWarehouseWallOpening[] array2 = new FishWarehouseWallOpening[array.Length]; for (int num = 0; num < array.Length; num++) { if (!TryNormalizeOpening(array[num], item, item2, out FishWarehouseWallOpening normalized)) { return false; } array2[num] = normalized; } Array.Sort(array2, delegate(FishWarehouseWallOpening left, FishWarehouseWallOpening right) { int num5 = left.HorizontalMinimum.CompareTo(right.HorizontalMinimum); return (num5 == 0) ? left.HorizontalMaximum.CompareTo(right.HorizontalMaximum) : num5; }); for (int num2 = 1; num2 < array2.Length; num2++) { float num3 = array2[num2].HorizontalMinimum - array2[num2 - 1].HorizontalMaximum; if (num3 < 0f || (num3 > 0f && num3 < 0.5f)) { return false; } } List list = new List(); if (array2.Length == 0) { list.Add(new FishWarehouseWallSegment(wall, item, item2, 0f, 5.8f)); } else { float horizontalMinimum = item; FishWarehouseWallOpening[] array3 = array2; foreach (FishWarehouseWallOpening fishWarehouseWallOpening in array3) { AddSegmentIfMaterial(list, wall, horizontalMinimum, fishWarehouseWallOpening.HorizontalMinimum, 0f, 5.8f); AddSegmentIfMaterial(list, wall, fishWarehouseWallOpening.HorizontalMinimum, fishWarehouseWallOpening.HorizontalMaximum, 0f, fishWarehouseWallOpening.Bottom); AddSegmentIfMaterial(list, wall, fishWarehouseWallOpening.HorizontalMinimum, fishWarehouseWallOpening.HorizontalMaximum, fishWarehouseWallOpening.Top, 5.8f); horizontalMinimum = fishWarehouseWallOpening.HorizontalMaximum; } AddSegmentIfMaterial(list, wall, horizontalMinimum, item2, 0f, 5.8f); } if (list.Any((FishWarehouseWallSegment segment) => segment.Width <= 0.001f || segment.Height <= 0.001f)) { return false; } plan = new FishWarehouseWallPlan(wall, list.AsReadOnly()); return true; } private static bool TryNormalizeOpening(FishWarehouseWallOpening opening, float minimum, float maximum, out FishWarehouseWallOpening normalized) { normalized = null; if (!float.IsFinite(opening.HorizontalMinimum) || !float.IsFinite(opening.HorizontalMaximum) || !float.IsFinite(opening.Bottom) || !float.IsFinite(opening.Top)) { return false; } float num = SnapToEdge(opening.HorizontalMinimum, minimum); float num2 = SnapToEdge(opening.HorizontalMaximum, maximum); if (num < minimum || num2 > maximum || num >= num2 || num2 - num <= 0.001f || opening.Bottom < 0f || opening.Top > 5.8f || opening.Bottom >= opening.Top || opening.Top - opening.Bottom <= 0.001f) { return false; } float num3 = num - minimum; float num4 = maximum - num2; bool flag = MathF.Abs(num - minimum) <= 0.001f; bool flag2 = MathF.Abs(num2 - maximum) <= 0.001f; if ((flag && !opening.IsCornerTerminatedAtMinimum) || (flag2 && !opening.IsCornerTerminatedAtMaximum)) { return false; } if ((opening.IsCornerTerminatedAtMinimum && !flag) || (opening.IsCornerTerminatedAtMaximum && !flag2)) { return false; } if (flag) { if (num4 < 0.5f) { return false; } } else if (flag2) { if (num3 < 0.5f) { return false; } } else if (num3 < 0.5f || num4 < 0.5f) { return false; } normalized = opening with { HorizontalMinimum = num, HorizontalMaximum = num2, IsCornerTerminatedAtMinimum = flag, IsCornerTerminatedAtMaximum = flag2 }; return true; } private static void AddSegmentIfMaterial(ICollection segments, FishWarehouseInteriorDoorSide wall, float horizontalMinimum, float horizontalMaximum, float bottom, float top) { if (!(horizontalMaximum - horizontalMinimum <= 0.001f) && !(top - bottom <= 0.001f)) { segments.Add(new FishWarehouseWallSegment(wall, horizontalMinimum, horizontalMaximum, bottom, top)); } } private static float SnapToEdge(float value, float edge) { if (!(MathF.Abs(value - edge) <= 0.001f)) { return value; } return edge; } private static (float Minimum, float Maximum) GetHorizontalBounds(FishWarehouseInteriorDoorSide wall) { if ((uint)wall > 1u) { return (Minimum: -11.6f, Maximum: 11.6f); } return (Minimum: -6.9f, Maximum: 6.9f); } } public sealed record LocalPressureDecayInput(LocalPressureState State, double CurrentGameTimeHours, bool ActivePursuit); public enum LocalPressureDecayPauseReason { None, QuietGrace, ActivePursuit, TimeRewound, NoElapsedTime, NoHeat, NoWholePointAccrued } public sealed record LocalPressureDecayResult(LocalPressureState State, int HeatDelta, double EligibleDecayHours, bool CatchUpWasCapped, LocalPressureDecayPauseReason PauseReason); public static class LocalPressureDecay { public static LocalPressureDecayResult Evaluate(LocalPressureDecayInput input, LocalPressureProfile profile) { ArgumentNullException.ThrowIfNull(input, "input"); ArgumentNullException.ThrowIfNull(input.State, "input.State"); ArgumentNullException.ThrowIfNull(profile, "profile"); ValidateGameTime(input.CurrentGameTimeHours); LocalPressureState state = input.State; if (state.LastDecayEvaluation.HasValue && input.CurrentGameTimeHours < state.LastDecayEvaluation.Value) { return new LocalPressureDecayResult(state, 0, 0.0, CatchUpWasCapped: false, LocalPressureDecayPauseReason.TimeRewound); } int heatFloor = profile.GetHeatFloor(state.KnownOffender); int num = Math.Max(0, state.LocalHeat - heatFloor); if (num == 0) { return new LocalPressureDecayResult(state, 0, 0.0, CatchUpWasCapped: false, LocalPressureDecayPauseReason.NoHeat); } double? num2 = state.LastDecayEvaluation ?? state.LastEvidenceGameTime; if (input.ActivePursuit) { return new LocalPressureDecayResult(AdvanceEvaluationMarker(state, input.CurrentGameTimeHours), 0, 0.0, CatchUpWasCapped: false, LocalPressureDecayPauseReason.ActivePursuit); } if (!num2.HasValue) { return new LocalPressureDecayResult(AdvanceEvaluationMarker(state, input.CurrentGameTimeHours), 0, 0.0, CatchUpWasCapped: false, LocalPressureDecayPauseReason.NoElapsedTime); } if (input.CurrentGameTimeHours <= num2.Value) { return new LocalPressureDecayResult(state, 0, 0.0, CatchUpWasCapped: false, LocalPressureDecayPauseReason.NoElapsedTime); } if (state.QuietGraceUntil.HasValue && input.CurrentGameTimeHours < state.QuietGraceUntil.Value) { return new LocalPressureDecayResult(state, 0, 0.0, CatchUpWasCapped: false, LocalPressureDecayPauseReason.QuietGrace); } double num3 = Math.Max(num2.Value, state.QuietGraceUntil ?? num2.Value); double num4 = Math.Max(0.0, input.CurrentGameTimeHours - num3); double num5 = Math.Min(num4, profile.MaximumDecayCatchUpHours); bool catchUpWasCapped = num4 > profile.MaximumDecayCatchUpHours; int val = (int)Math.Floor(num5 * profile.HeatDecayPerHour); if (profile.HeatDecayPerHour == 0.0) { return new LocalPressureDecayResult(state, 0, num5, catchUpWasCapped, LocalPressureDecayPauseReason.NoHeat); } int num6 = Math.Min(val, num); if (num6 == 0) { return new LocalPressureDecayResult(state, 0, num5, catchUpWasCapped, LocalPressureDecayPauseReason.NoWholePointAccrued); } int num7 = -num6; double consumedEvaluationMarker = GetConsumedEvaluationMarker(state, input.CurrentGameTimeHours, num3, num6, num, catchUpWasCapped, profile); LocalPressureState state2 = new LocalPressureState(state.LocalHeat + num7, state.KnownOffender, state.LastEvidenceGameTime, state.QuietGraceUntil, consumedEvaluationMarker, state.PlayerId, state.Region, state.PropertyCode, checked(state.Revision + 1)); LocalPressureDecayPauseReason pauseReason = ((num7 == 0) ? LocalPressureDecayPauseReason.NoWholePointAccrued : LocalPressureDecayPauseReason.None); return new LocalPressureDecayResult(state2, num7, num5, catchUpWasCapped, pauseReason); } private static double GetConsumedEvaluationMarker(LocalPressureState state, double currentGameTimeHours, double decayStart, int pointsCredited, int removableHeat, bool catchUpWasCapped, LocalPressureProfile profile) { if (catchUpWasCapped) { return currentGameTimeHours; } if (removableHeat == 0 || profile.HeatDecayPerHour == 0.0) { return currentGameTimeHours; } if (pointsCredited == 0) { return decayStart; } if (pointsCredited >= removableHeat) { return currentGameTimeHours; } return Math.Min(currentGameTimeHours, decayStart + (double)pointsCredited / profile.HeatDecayPerHour); } private static LocalPressureState AdvanceEvaluationMarker(LocalPressureState state, double currentGameTimeHours) { if (state.LastDecayEvaluation != currentGameTimeHours) { return new LocalPressureState(state.LocalHeat, state.KnownOffender, state.LastEvidenceGameTime, state.QuietGraceUntil, currentGameTimeHours, state.PlayerId, state.Region, state.PropertyCode, checked(state.Revision + 1)); } return state; } private static void ValidateGameTime(double gameTimeHours) { if (double.IsNaN(gameTimeHours) || double.IsInfinity(gameTimeHours) || gameTimeHours < 0.0) { throw new ArgumentOutOfRangeException("gameTimeHours"); } } } public sealed record LocalPressureEvidenceEvent(LocalPressureReasonCode ReasonCode, double GameTimeHours, string? PlayerId = null, string? Region = null, string? PropertyCode = null, string? CorrelationId = null, double? ReceivedGameTimeHours = null); public sealed record LocalPressureTransitionResult(LocalPressureState State, int HeatDelta, LocalPressureTier PreviousTier, LocalPressureTier CurrentTier, LocalPressureReasonCode ReasonCode); public sealed record LocalPressureRecordWipeResult(LocalPressureState State, int HeatDelta, LocalPressureTier PreviousTier, LocalPressureTier CurrentTier); public sealed record LocalPressureProfile { public static LocalPressureProfile Moderate { get; } = new LocalPressureProfile(24, 49, 74, 25, 2.0, 1.0, 24.0, 1.0, 6, new KeyValuePair[7] { Delta(LocalPressureReasonCode.WitnessedCrime, 8), Delta(LocalPressureReasonCode.ContrabandDiscovered, 12), Delta(LocalPressureReasonCode.Arrest, 20), Delta(LocalPressureReasonCode.ResistanceOrViolence, 12), Delta(LocalPressureReasonCode.PursuitEscalation, 1), Delta(LocalPressureReasonCode.EvadedPursuit, 5), Delta(LocalPressureReasonCode.VerifiedCurfewOrExposure, 6) }); public static LocalPressureProfile Forgiving { get; } = new LocalPressureProfile(14, 49, 79, 15, 1.0, 2.0, 24.0, 1.5, 4, new KeyValuePair[7] { Delta(LocalPressureReasonCode.WitnessedCrime, 6), Delta(LocalPressureReasonCode.ContrabandDiscovered, 8), Delta(LocalPressureReasonCode.Arrest, 15), Delta(LocalPressureReasonCode.ResistanceOrViolence, 8), Delta(LocalPressureReasonCode.PursuitEscalation, 1), Delta(LocalPressureReasonCode.EvadedPursuit, 3), Delta(LocalPressureReasonCode.VerifiedCurfewOrExposure, 4) }); public static LocalPressureProfile Punishing { get; } = new LocalPressureProfile(34, 59, 79, 35, 3.0, 0.5, 24.0, 0.75, 8, new KeyValuePair[7] { Delta(LocalPressureReasonCode.WitnessedCrime, 12), Delta(LocalPressureReasonCode.ContrabandDiscovered, 16), Delta(LocalPressureReasonCode.Arrest, 25), Delta(LocalPressureReasonCode.ResistanceOrViolence, 18), Delta(LocalPressureReasonCode.PursuitEscalation, 1), Delta(LocalPressureReasonCode.EvadedPursuit, 8), Delta(LocalPressureReasonCode.VerifiedCurfewOrExposure, 10) }); public int QuietUpperBound { get; } public int NoticedLowerBound => QuietUpperBound + 1; public int NoticedUpperBound { get; } public int WatchedLowerBound => NoticedUpperBound + 1; public int WatchedUpperBound { get; } public int CriticalLowerBound => WatchedUpperBound + 1; public int KnownOffenderFloor { get; } public double QuietGraceHours { get; } public double HeatDecayPerHour { get; } public double MaximumDecayCatchUpHours { get; } public double PursuitContributionIntervalHours { get; } public int PursuitHeatCap { get; } public IReadOnlyDictionary HeatDeltas { get; } public const int MinimumHeat = 0; public const int MaximumHeat = 100; public LocalPressureProfile(int quietUpperBound, int noticedUpperBound, int watchedUpperBound, int knownOffenderFloor, double quietGraceHours, double heatDecayPerHour, double maximumDecayCatchUpHours, double pursuitContributionIntervalHours, int pursuitHeatCap, IEnumerable> heatDeltas) { QuietUpperBound = quietUpperBound; NoticedUpperBound = noticedUpperBound; WatchedUpperBound = watchedUpperBound; KnownOffenderFloor = knownOffenderFloor; QuietGraceHours = quietGraceHours; HeatDecayPerHour = heatDecayPerHour; MaximumDecayCatchUpHours = maximumDecayCatchUpHours; PursuitContributionIntervalHours = pursuitContributionIntervalHours; PursuitHeatCap = pursuitHeatCap; HeatDeltas = CreateHeatDeltas(heatDeltas); Validate(); } public int GetHeatFloor(bool knownOffender) { if (!knownOffender) { return 0; } return KnownOffenderFloor; } public int GetHeatDelta(LocalPressureReasonCode reasonCode) { if (!Enum.IsDefined(typeof(LocalPressureReasonCode), reasonCode)) { throw new ArgumentOutOfRangeException("reasonCode", reasonCode, "Unknown Local Pressure reason code."); } if (!HeatDeltas.TryGetValue(reasonCode, out var value)) { throw new InvalidOperationException($"No heat delta is configured for Local Pressure reason code {reasonCode}."); } return value; } private static KeyValuePair Delta(LocalPressureReasonCode reasonCode, int heatDelta) { return new KeyValuePair(reasonCode, heatDelta); } private static IReadOnlyDictionary CreateHeatDeltas(IEnumerable> heatDeltas) { ArgumentNullException.ThrowIfNull(heatDeltas, "heatDeltas"); Dictionary configuredDeltas = new Dictionary(); foreach (KeyValuePair heatDelta in heatDeltas) { if (!Enum.IsDefined(typeof(LocalPressureReasonCode), heatDelta.Key)) { throw new ArgumentOutOfRangeException("heatDeltas", heatDelta.Key, "Unknown Local Pressure reason code."); } if (!configuredDeltas.TryAdd(heatDelta.Key, heatDelta.Value)) { throw new ArgumentException($"Duplicate heat delta configured for Local Pressure reason code {heatDelta.Key}.", "heatDeltas"); } if (heatDelta.Value < 0) { throw new ArgumentOutOfRangeException("heatDeltas", heatDelta.Value, $"Heat delta for Local Pressure reason code {heatDelta.Key} must be non-negative."); } } LocalPressureReasonCode[] array = (from reasonCode in Enum.GetValues() where !configuredDeltas.ContainsKey(reasonCode) select reasonCode).ToArray(); if (array.Length != 0) { throw new ArgumentException("Missing heat delta configuration for Local Pressure reason code(s): " + string.Join(", ", array) + ".", "heatDeltas"); } return new ReadOnlyDictionary(configuredDeltas); } private void Validate() { if (QuietUpperBound < 0 || QuietUpperBound >= NoticedUpperBound || NoticedUpperBound >= WatchedUpperBound || WatchedUpperBound >= 100) { throw new ArgumentOutOfRangeException("QuietUpperBound", "Tier thresholds must cover 0 through 100 in ascending order."); } if (KnownOffenderFloor < NoticedLowerBound || KnownOffenderFloor > 100) { throw new ArgumentOutOfRangeException("KnownOffenderFloor", "Known Offender floor must be within the Noticed-or-higher range."); } if (QuietGraceHours < 0.0 || double.IsNaN(QuietGraceHours) || double.IsInfinity(QuietGraceHours)) { throw new ArgumentOutOfRangeException("QuietGraceHours"); } if (HeatDecayPerHour < 0.0 || double.IsNaN(HeatDecayPerHour) || double.IsInfinity(HeatDecayPerHour)) { throw new ArgumentOutOfRangeException("HeatDecayPerHour"); } if (MaximumDecayCatchUpHours < 0.0 || double.IsNaN(MaximumDecayCatchUpHours) || double.IsInfinity(MaximumDecayCatchUpHours)) { throw new ArgumentOutOfRangeException("MaximumDecayCatchUpHours"); } if (PursuitContributionIntervalHours <= 0.0 || double.IsNaN(PursuitContributionIntervalHours) || double.IsInfinity(PursuitContributionIntervalHours)) { throw new ArgumentOutOfRangeException("PursuitContributionIntervalHours"); } if (PursuitHeatCap < 0) { throw new ArgumentOutOfRangeException("PursuitHeatCap"); } } [CompilerGenerated] private LocalPressureProfile(LocalPressureProfile original) { QuietUpperBound = original.QuietUpperBound; NoticedUpperBound = original.NoticedUpperBound; WatchedUpperBound = original.WatchedUpperBound; KnownOffenderFloor = original.KnownOffenderFloor; QuietGraceHours = original.QuietGraceHours; HeatDecayPerHour = original.HeatDecayPerHour; MaximumDecayCatchUpHours = original.MaximumDecayCatchUpHours; PursuitContributionIntervalHours = original.PursuitContributionIntervalHours; PursuitHeatCap = original.PursuitHeatCap; HeatDeltas = original.HeatDeltas; } } public sealed record LocalPressurePursuitState(string PursuitId, double StartedGameTimeHours, double LastCreditedGameTimeHours, int CreditedHeat, bool IsActive) { public static LocalPressurePursuitState Begin(string pursuitId, double gameTimeHours) { if (string.IsNullOrWhiteSpace(pursuitId)) { throw new ArgumentException("Pursuit identity is required.", "pursuitId"); } if (double.IsNaN(gameTimeHours) || double.IsInfinity(gameTimeHours) || gameTimeHours < 0.0) { throw new ArgumentOutOfRangeException("gameTimeHours"); } return new LocalPressurePursuitState(pursuitId, gameTimeHours, gameTimeHours, 0, IsActive: true); } public LocalPressurePursuitState End() { return this with { IsActive = false }; } } public sealed record LocalPressurePursuitEvaluation(LocalPressurePursuitState State, int HeatDelta); public static class LocalPressurePursuitAccumulator { public static LocalPressurePursuitEvaluation Evaluate(LocalPressurePursuitState state, double currentGameTimeHours, LocalPressureProfile profile) { ArgumentNullException.ThrowIfNull(state, "state"); ArgumentNullException.ThrowIfNull(profile, "profile"); if (double.IsNaN(currentGameTimeHours) || double.IsInfinity(currentGameTimeHours) || currentGameTimeHours < 0.0) { throw new ArgumentOutOfRangeException("currentGameTimeHours"); } if (!state.IsActive || currentGameTimeHours <= state.LastCreditedGameTimeHours || state.CreditedHeat >= profile.PursuitHeatCap) { return new LocalPressurePursuitEvaluation(state, 0); } int num = (int)Math.Floor((currentGameTimeHours - state.LastCreditedGameTimeHours) / profile.PursuitContributionIntervalHours); int heatDelta = profile.GetHeatDelta(LocalPressureReasonCode.PursuitEscalation); if (num <= 0 || heatDelta <= 0) { return new LocalPressurePursuitEvaluation(state, 0); } int num2 = profile.PursuitHeatCap - state.CreditedHeat; int val = (num2 + heatDelta - 1) / heatDelta; int num3 = Math.Min(num, val); int num4 = Math.Min(num2, checked(num3 * heatDelta)); double lastCreditedGameTimeHours = state.LastCreditedGameTimeHours + (double)num3 * profile.PursuitContributionIntervalHours; return new LocalPressurePursuitEvaluation(state with { LastCreditedGameTimeHours = lastCreditedGameTimeHours, CreditedHeat = state.CreditedHeat + num4 }, num4); } } public enum LocalPressureReasonCode { WitnessedCrime, ContrabandDiscovered, Arrest, ResistanceOrViolence, PursuitEscalation, EvadedPursuit, VerifiedCurfewOrExposure } public sealed record LocalPressureState { public int LocalHeat { get; } public bool KnownOffender { get; } public double? LastEvidenceGameTime { get; } public double? QuietGraceUntil { get; } public double? LastDecayEvaluation { get; } public string? PlayerId { get; } public string? Region { get; } public string? PropertyCode { get; } public long Revision { get; } public LocalPressureState(int LocalHeat, bool KnownOffender, double? LastEvidenceGameTime, double? QuietGraceUntil, double? LastDecayEvaluation, string? PlayerId, string? Region, string? PropertyCode, long Revision) { if (LocalHeat < 0 || LocalHeat > 100) { throw new ArgumentOutOfRangeException("LocalHeat", "Local Heat must be between 0 and 100."); } if (Revision < 0) { throw new ArgumentOutOfRangeException("Revision", "State revision cannot be negative."); } ValidateGameTime(LastEvidenceGameTime, "LastEvidenceGameTime"); ValidateGameTime(QuietGraceUntil, "QuietGraceUntil"); ValidateGameTime(LastDecayEvaluation, "LastDecayEvaluation"); this.LocalHeat = LocalHeat; this.KnownOffender = KnownOffender; this.LastEvidenceGameTime = LastEvidenceGameTime; this.QuietGraceUntil = QuietGraceUntil; this.LastDecayEvaluation = LastDecayEvaluation; this.PlayerId = PlayerId; this.Region = Region; this.PropertyCode = PropertyCode; this.Revision = Revision; } public static LocalPressureState Quiet(string? playerId = null) { return new LocalPressureState(0, KnownOffender: false, null, null, null, playerId, null, null, 0L); } private static void ValidateGameTime(double? gameTimeHours, string parameterName) { if (gameTimeHours.HasValue && (double.IsNaN(gameTimeHours.Value) || double.IsInfinity(gameTimeHours.Value) || gameTimeHours.Value < 0.0)) { throw new ArgumentOutOfRangeException(parameterName, "Game time must be finite and non-negative."); } } } public enum LocalPressureTier { Quiet, Noticed, Watched, Critical } public static class LocalPressureTransitions { public static LocalPressureTransitionResult ApplyEvidence(LocalPressureState state, LocalPressureEvidenceEvent evidence, LocalPressureProfile profile) { ArgumentNullException.ThrowIfNull(profile, "profile"); return ApplyEvidence(state, evidence, profile, profile.GetHeatDelta(evidence.ReasonCode)); } public static LocalPressureTransitionResult ApplyEvidence(LocalPressureState state, LocalPressureEvidenceEvent evidence, LocalPressureProfile profile, int evidenceHeatDelta) { ArgumentNullException.ThrowIfNull(state, "state"); ArgumentNullException.ThrowIfNull(evidence, "evidence"); ArgumentNullException.ThrowIfNull(profile, "profile"); if (double.IsNaN(evidence.GameTimeHours) || double.IsInfinity(evidence.GameTimeHours) || evidence.GameTimeHours < 0.0) { throw new ArgumentOutOfRangeException("evidence", "Evidence game time must be finite and non-negative."); } double num = evidence.ReceivedGameTimeHours ?? evidence.GameTimeHours; if (double.IsNaN(num) || double.IsInfinity(num) || num < 0.0) { throw new ArgumentOutOfRangeException("evidence", "Evidence receipt game time must be finite and non-negative."); } if (evidenceHeatDelta < 0) { throw new ArgumentOutOfRangeException("evidenceHeatDelta", "Evidence heat delta cannot be negative."); } if (state.PlayerId != null && evidence.PlayerId != null && !string.Equals(state.PlayerId, evidence.PlayerId, StringComparison.Ordinal)) { throw new ArgumentException("Evidence player identity does not match the Local Pressure state.", "evidence"); } LocalPressureTier tier = GetTier(state.LocalHeat, profile); int num2 = ClampHeat(state.LocalHeat + evidenceHeatDelta); bool flag = state.KnownOffender || evidence.ReasonCode == LocalPressureReasonCode.Arrest; if (flag) { num2 = Math.Max(num2, profile.GetHeatFloor(flag)); } double value = ((!state.LastEvidenceGameTime.HasValue) ? evidence.GameTimeHours : Math.Max(state.LastEvidenceGameTime.Value, evidence.GameTimeHours)); double value2 = Math.Max(state.QuietGraceUntil ?? double.MinValue, num + profile.QuietGraceHours); return new LocalPressureTransitionResult(new LocalPressureState(num2, flag, value, value2, state.LastDecayEvaluation, evidence.PlayerId ?? state.PlayerId, evidence.Region ?? state.Region, evidence.PropertyCode ?? state.PropertyCode, checked(state.Revision + 1)), num2 - state.LocalHeat, tier, GetTier(num2, profile), evidence.ReasonCode); } public static LocalPressureRecordWipeResult ApplyRecordWipe(LocalPressureState state, LocalPressureProfile profile) { ArgumentNullException.ThrowIfNull(state, "state"); ArgumentNullException.ThrowIfNull(profile, "profile"); LocalPressureTier tier = GetTier(state.LocalHeat, profile); LocalPressureState localPressureState = new LocalPressureState(0, KnownOffender: false, state.LastEvidenceGameTime, state.QuietGraceUntil, state.LastDecayEvaluation, state.PlayerId, state.Region, state.PropertyCode, checked(state.Revision + 1)); return new LocalPressureRecordWipeResult(localPressureState, -state.LocalHeat, tier, GetTier(localPressureState.LocalHeat, profile)); } public static LocalPressureTier GetTier(int heat, LocalPressureProfile profile) { ArgumentNullException.ThrowIfNull(profile, "profile"); heat = ClampHeat(heat); if (heat <= profile.QuietUpperBound) { return LocalPressureTier.Quiet; } if (heat <= profile.NoticedUpperBound) { return LocalPressureTier.Noticed; } if (heat <= profile.WatchedUpperBound) { return LocalPressureTier.Watched; } return LocalPressureTier.Critical; } private static int ClampHeat(int heat) { return Math.Clamp(heat, 0, 100); } } public enum Release1ChiefState { Adopted, DemandOpen, Paying, Settled, Declined } public sealed record Release1ChiefRecord { public bool Adopted { get; init; } public LocalPressureTier AdoptedTier { get; init; } public bool AdoptedKnownOffender { get; init; } public int DemandRound { get; init; } public Release1ChiefState State { get; init; } public int WatchLinesSent { get; init; } public int LockdownAnnouncements { get; init; } public bool LockdownEngaged { get; init; } public int PaidLifts { get; init; } public int CooledLifts { get; init; } public int DeclineRepliesSent { get; init; } public int PaymentBlockedNotices { get; init; } public double? LastShortfallNoticed { get; init; } public IReadOnlyList AcceptedLogicalCorrelations { get { return _acceptedLogicalCorrelations; } init { _acceptedLogicalCorrelations = Freeze(value, "AcceptedLogicalCorrelations"); } } public IReadOnlyList NativeEffectIds { get { return _nativeEffectIds; } init { _nativeEffectIds = Freeze(value, "NativeEffectIds"); } } public long Revision { get; init; } public int Demand => Release1ChiefDemandLadder.For(DemandRound); public const int MaximumDemandRound = 3; private IReadOnlyList _acceptedLogicalCorrelations = ImmutableArray.Empty; private IReadOnlyList _nativeEffectIds = ImmutableArray.Empty; public Release1ChiefRecord(bool Adopted, LocalPressureTier AdoptedTier, bool AdoptedKnownOffender, int DemandRound, Release1ChiefState State, int WatchLinesSent, int LockdownAnnouncements, bool LockdownEngaged, int PaidLifts, int CooledLifts, int DeclineRepliesSent, int PaymentBlockedNotices, double? LastShortfallNoticed, IReadOnlyList AcceptedLogicalCorrelations, IReadOnlyList NativeEffectIds, long Revision) { this.Adopted = Adopted; this.AdoptedTier = AdoptedTier; this.AdoptedKnownOffender = AdoptedKnownOffender; this.DemandRound = DemandRound; this.State = State; this.WatchLinesSent = WatchLinesSent; this.LockdownAnnouncements = LockdownAnnouncements; this.LockdownEngaged = LockdownEngaged; this.PaidLifts = PaidLifts; this.CooledLifts = CooledLifts; this.DeclineRepliesSent = DeclineRepliesSent; this.PaymentBlockedNotices = PaymentBlockedNotices; this.LastShortfallNoticed = LastShortfallNoticed; this.AcceptedLogicalCorrelations = Freeze(AcceptedLogicalCorrelations, "AcceptedLogicalCorrelations"); this.NativeEffectIds = Freeze(NativeEffectIds, "NativeEffectIds"); this.Revision = Revision; Validate(); } public static Release1ChiefRecord Adopt(LocalPressureTier tier, bool knownOffender) { return new Release1ChiefRecord(Adopted: true, tier, knownOffender, 0, Release1ChiefState.Adopted, 0, 0, LockdownEngaged: false, 0, 0, 0, 0, null, Array.Empty(), Array.Empty(), 0L); } public void Validate() { if (!Adopted) { throw new ArgumentException("A Chief record only exists once it has been adopted.", "Adopted"); } if (!Enum.IsDefined(AdoptedTier)) { throw new ArgumentException("Adopted tier is not defined.", "AdoptedTier"); } if (!Enum.IsDefined(State)) { throw new ArgumentException("Chief state is not defined.", "State"); } if (DemandRound < 0 || DemandRound > 3) { throw new ArgumentException("Chief demand round must be between 0 and the cap.", "DemandRound"); } if (State == Release1ChiefState.Adopted != (DemandRound == 0)) { throw new ArgumentException("Round zero is exactly the adopted state.", "DemandRound"); } if (WatchLinesSent < 0 || LockdownAnnouncements < 0 || PaidLifts < 0 || CooledLifts < 0 || DeclineRepliesSent < 0 || PaymentBlockedNotices < 0) { throw new ArgumentException("Chief message counters cannot be negative.", "WatchLinesSent"); } if (Revision < 0) { throw new ArgumentException("Chief revision cannot be negative.", "Revision"); } if (LockdownEngaged && LockdownAnnouncements < 1) { throw new ArgumentException("A lockdown can only be engaged after it has been announced.", "LockdownEngaged"); } double? lastShortfallNoticed = LastShortfallNoticed; if (lastShortfallNoticed.HasValue) { double valueOrDefault = lastShortfallNoticed.GetValueOrDefault(); if (double.IsNaN(valueOrDefault) || double.IsInfinity(valueOrDefault) || valueOrDefault < 0.0) { throw new ArgumentException("A noticed shortfall must be finite and non negative.", "LastShortfallNoticed"); } } foreach (string acceptedLogicalCorrelation in AcceptedLogicalCorrelations) { if (!Release1LogicalCorrelation.TryParse(acceptedLogicalCorrelation, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.chief-campbell" || correlation.TransitionKind != Release1TransitionKind.ChiefPaymentAccepted || correlation.Attempt < 1 || correlation.Attempt > DemandRound) { throw new ArgumentException("Chief correlations must be canonical payment authorizations for a round already reached.", "AcceptedLogicalCorrelations"); } } foreach (string nativeEffectId in NativeEffectIds) { Release1MissionRecord.ValidateId(nativeEffectId, "NativeEffectIds"); } if (AcceptedLogicalCorrelations.Distinct(StringComparer.Ordinal).Count() != AcceptedLogicalCorrelations.Count || NativeEffectIds.Distinct(StringComparer.Ordinal).Count() != NativeEffectIds.Count) { throw new ArgumentException("Chief collections must be unique.", "NativeEffectIds"); } if (State == Release1ChiefState.Paying && (AcceptedLogicalCorrelations.Count == 0 || NativeEffectIds.Count == 0)) { throw new ArgumentException("A paying Chief record requires its authorization and its effect.", "State"); } } public bool ValueEquals(Release1ChiefRecord? other) { if ((object)other != null && Adopted == other.Adopted && AdoptedTier == other.AdoptedTier && AdoptedKnownOffender == other.AdoptedKnownOffender && DemandRound == other.DemandRound && State == other.State && WatchLinesSent == other.WatchLinesSent && LockdownAnnouncements == other.LockdownAnnouncements && LockdownEngaged == other.LockdownEngaged && PaidLifts == other.PaidLifts && CooledLifts == other.CooledLifts && DeclineRepliesSent == other.DeclineRepliesSent && PaymentBlockedNotices == other.PaymentBlockedNotices && Nullable.Equals(LastShortfallNoticed, other.LastShortfallNoticed) && Revision == other.Revision && AcceptedLogicalCorrelations.SequenceEqual(other.AcceptedLogicalCorrelations, StringComparer.Ordinal)) { return NativeEffectIds.SequenceEqual(other.NativeEffectIds, StringComparer.Ordinal); } return false; } public bool Equals(Release1ChiefRecord? other) { return ValueEquals(other); } public override int GetHashCode() { HashCode hashCode = default(HashCode); hashCode.Add(Adopted); hashCode.Add(AdoptedTier); hashCode.Add(AdoptedKnownOffender); hashCode.Add(DemandRound); hashCode.Add(State); hashCode.Add(WatchLinesSent); hashCode.Add(LockdownAnnouncements); hashCode.Add(LockdownEngaged); hashCode.Add(PaidLifts); hashCode.Add(CooledLifts); hashCode.Add(DeclineRepliesSent); hashCode.Add(PaymentBlockedNotices); hashCode.Add(LastShortfallNoticed); hashCode.Add(Revision); foreach (string acceptedLogicalCorrelation in AcceptedLogicalCorrelations) { hashCode.Add(acceptedLogicalCorrelation); } foreach (string nativeEffectId in NativeEffectIds) { hashCode.Add(nativeEffectId); } return hashCode.ToHashCode(); } private static IReadOnlyList Freeze(IReadOnlyList? values, string parameterName) { if (values == null) { throw new ArgumentNullException(parameterName); } return ImmutableArray.CreateRange(values); } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("Adopted = "); builder.Append(Adopted.ToString()); builder.Append(", AdoptedTier = "); builder.Append(AdoptedTier.ToString()); builder.Append(", AdoptedKnownOffender = "); builder.Append(AdoptedKnownOffender.ToString()); builder.Append(", DemandRound = "); builder.Append(DemandRound.ToString()); builder.Append(", State = "); builder.Append(State.ToString()); builder.Append(", WatchLinesSent = "); builder.Append(WatchLinesSent.ToString()); builder.Append(", LockdownAnnouncements = "); builder.Append(LockdownAnnouncements.ToString()); builder.Append(", LockdownEngaged = "); builder.Append(LockdownEngaged.ToString()); builder.Append(", PaidLifts = "); builder.Append(PaidLifts.ToString()); builder.Append(", CooledLifts = "); builder.Append(CooledLifts.ToString()); builder.Append(", DeclineRepliesSent = "); builder.Append(DeclineRepliesSent.ToString()); builder.Append(", PaymentBlockedNotices = "); builder.Append(PaymentBlockedNotices.ToString()); builder.Append(", LastShortfallNoticed = "); builder.Append(LastShortfallNoticed.ToString()); builder.Append(", AcceptedLogicalCorrelations = "); builder.Append(AcceptedLogicalCorrelations); builder.Append(", NativeEffectIds = "); builder.Append(NativeEffectIds); builder.Append(", Revision = "); builder.Append(Revision.ToString()); builder.Append(", Demand = "); builder.Append(Demand.ToString()); return true; } [CompilerGenerated] private Release1ChiefRecord(Release1ChiefRecord original) { Adopted = original.Adopted; AdoptedTier = original.AdoptedTier; AdoptedKnownOffender = original.AdoptedKnownOffender; DemandRound = original.DemandRound; State = original.State; WatchLinesSent = original.WatchLinesSent; LockdownAnnouncements = original.LockdownAnnouncements; LockdownEngaged = original.LockdownEngaged; PaidLifts = original.PaidLifts; CooledLifts = original.CooledLifts; DeclineRepliesSent = original.DeclineRepliesSent; PaymentBlockedNotices = original.PaymentBlockedNotices; LastShortfallNoticed = original.LastShortfallNoticed; _acceptedLogicalCorrelations = original._acceptedLogicalCorrelations; _nativeEffectIds = original._nativeEffectIds; Revision = original.Revision; } } public static class Release1ChiefDemandLadder { public static int For(int round) { if (round > 1) { if (round != 2) { return 25000; } return 20000; } return 15000; } } public sealed record Release1ConsignmentFingerprint(string ProductId, string PackagingId, int Quantity) { public void Validate() { Release1SmallCourtesyAssignment.ValidateStableId(ProductId, "ProductId"); Release1SmallCourtesyAssignment.ValidateStableId(PackagingId, "PackagingId"); if (Quantity < 1) { throw new ArgumentOutOfRangeException("Quantity", "A consignment is at least one unit."); } } public bool Matches(Release1SmallCourtesySlotSnapshot? slot) { if ((object)slot != null && slot.Quantity == Quantity && slot.IsPackaged && string.Equals(slot.ProductId, ProductId, StringComparison.Ordinal)) { return string.Equals(slot.PackagingId, PackagingId, StringComparison.Ordinal); } return false; } public bool MatchesIdentity(Release1SmallCourtesySlotSnapshot? slot) { if ((object)slot != null && slot.Quantity > 0 && slot.IsPackaged && string.Equals(slot.ProductId, ProductId, StringComparison.Ordinal)) { return string.Equals(slot.PackagingId, PackagingId, StringComparison.Ordinal); } return false; } } public enum Release1ContainerObservation { ZeroLength, Empty, ExactlyOne, Several, Contaminated } public static class Release1ConsignmentClassifier { public static Release1ContainerObservation Classify(IReadOnlyList? slots, Release1ConsignmentFingerprint fingerprint, out int matchedSlotIndex) { ArgumentNullException.ThrowIfNull(fingerprint, "fingerprint"); matchedSlotIndex = -1; if (slots == null || slots.Count == 0) { return Release1ContainerObservation.ZeroLength; } Release1SmallCourtesySlotSnapshot[] array = slots.Where(fingerprint.Matches).ToArray(); if (array.Length > 1) { return Release1ContainerObservation.Several; } if (array.Length == 1) { Release1SmallCourtesySlotSnapshot only = array[0]; if (!slots.All((Release1SmallCourtesySlotSnapshot slot) => (object)slot != null && (slot.SlotIndex == only.SlotIndex || slot.Quantity == 0))) { return Release1ContainerObservation.Contaminated; } matchedSlotIndex = only.SlotIndex; return Release1ContainerObservation.ExactlyOne; } if (!slots.All((Release1SmallCourtesySlotSnapshot slot) => (object)slot != null && slot.Quantity == 0)) { return Release1ContainerObservation.Contaminated; } return Release1ContainerObservation.Empty; } public static int CountMatches(IReadOnlyList? slots, Release1ConsignmentFingerprint fingerprint, out Release1SmallCourtesySlotSnapshot? single) { ArgumentNullException.ThrowIfNull(fingerprint, "fingerprint"); single = null; if (slots == null) { return 0; } Release1SmallCourtesySlotSnapshot[] array = slots.Where(fingerprint.Matches).ToArray(); if (array.Length == 1) { single = array[0]; } return array.Length; } public static int CountIdentityMatches(IReadOnlyList? slots, Release1ConsignmentFingerprint fingerprint, out Release1SmallCourtesySlotSnapshot? single) { ArgumentNullException.ThrowIfNull(fingerprint, "fingerprint"); single = null; if (slots == null) { return 0; } Release1SmallCourtesySlotSnapshot[] array = slots.Where(fingerprint.MatchesIdentity).ToArray(); if (array.Length == 1) { single = array[0]; } return array.Length; } } public static class Release1DeadDropSelection { public static bool TryChooseSingle(IReadOnlyList? drops, string? authorizationCorrelationId, out Release1SmallCourtesyDropCandidate? chosen) { chosen = null; Release1SmallCourtesyDropCandidate[] array = EmptyOrdered(drops); if (array.Length < 1) { return false; } chosen = array[SourceIndex(array.Length, Hash(authorizationCorrelationId))]; return true; } public static bool TryChoosePair(IReadOnlyList? drops, string? authorizationCorrelationId, out Release1SmallCourtesyDropCandidate? source, out Release1SmallCourtesyDropCandidate? handoff) { source = null; handoff = null; Release1SmallCourtesyDropCandidate[] array = EmptyOrdered(drops); if (array.Length < 2) { return false; } ulong num = Hash(authorizationCorrelationId); int sourceIndex = SourceIndex(array.Length, num); source = array[sourceIndex]; Release1SmallCourtesyDropCandidate[] array2 = array.Where((Release1SmallCourtesyDropCandidate _, int index) => index != sourceIndex).ToArray(); handoff = array2[(uint)((num >> 8) % (ulong)array2.Length)]; return true; } private static Release1SmallCourtesyDropCandidate[] EmptyOrdered(IReadOnlyList? drops) { if (drops != null) { return drops.Where((Release1SmallCourtesyDropCandidate candidate) => candidate?.IsEmpty ?? false).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal).ToArray(); } return Array.Empty(); } private static ulong Hash(string? authorizationCorrelationId) { return BinaryPrimitives.ReadUInt64BigEndian(SHA256.HashData(Encoding.UTF8.GetBytes(authorizationCorrelationId ?? string.Empty)).AsSpan(0, 8)); } private static int SourceIndex(int count, ulong hash) { return (int)(hash % (ulong)count); } } public enum Release1KeepTheLightsOffAssignmentMode { Primary, MakeGood, Recovery } public enum Release1KeepTheLightsOffSelectionStatus { Selected, NoOwnedProperty, NoAssignedEmployee, InvalidInput } public sealed record Release1KeepTheLightsOffAssignment { public string MissionKey { get { return _missionKey; } init { if (!string.Equals(value, "release1.keep-the-lights-off", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Keep the Lights Off.", "MissionKey"); } _missionKey = value; } } public int Attempt { get { return _attempt; } init { if (value < 1) { throw new ArgumentOutOfRangeException("Attempt"); } _attempt = value; ValidateAuthorizationIfPresent(); } } public Release1KeepTheLightsOffAssignmentMode Mode { get { return _mode; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } _mode = value; ValidateAuthorizationIfPresent(); } } public string AuthorizationCorrelationId { get { return _authorizationCorrelationId; } init { ValidateAuthorization(value, Attempt, Mode); _authorizationCorrelationId = value; } } public int ExpectedOwnedPropertyCount { get { return _expectedOwnedPropertyCount; } init { if (value < 1) { throw new ArgumentOutOfRangeException("ExpectedOwnedPropertyCount", "At least one owned property is required."); } _expectedOwnedPropertyCount = value; } } public double WindowDurationGameMinutes { get { return _windowDurationGameMinutes; } init { if (!double.IsFinite(value) || value != 1440.0) { throw new ArgumentOutOfRangeException("WindowDurationGameMinutes", "The quiet window is exactly one in-game day."); } _windowDurationGameMinutes = value; } } public double? DeadlineGameMinutes { get { return _deadlineGameMinutes; } init { ValidateDeadline(value, Mode); _deadlineGameMinutes = value; } } public const double WindowGameMinutes = 1440.0; public const double TimedStageGameMinutes = 4320.0; private string _missionKey = string.Empty; private int _attempt; private Release1KeepTheLightsOffAssignmentMode _mode; private string _authorizationCorrelationId = string.Empty; private int _expectedOwnedPropertyCount; private double _windowDurationGameMinutes; private double? _deadlineGameMinutes; public Release1KeepTheLightsOffAssignment(string MissionKey, int Attempt, Release1KeepTheLightsOffAssignmentMode Mode, string AuthorizationCorrelationId, int ExpectedOwnedPropertyCount, double WindowDurationGameMinutes, double? DeadlineGameMinutes) { this.MissionKey = MissionKey; this.Attempt = Attempt; this.Mode = Mode; this.AuthorizationCorrelationId = AuthorizationCorrelationId; this.ExpectedOwnedPropertyCount = ExpectedOwnedPropertyCount; this.WindowDurationGameMinutes = WindowDurationGameMinutes; this.DeadlineGameMinutes = DeadlineGameMinutes; Validate(); } public void Validate() { if (!string.Equals(MissionKey, "release1.keep-the-lights-off", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Keep the Lights Off.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!Enum.IsDefined(Mode)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } ValidateAuthorization(AuthorizationCorrelationId, Attempt, Mode); if (ExpectedOwnedPropertyCount < 1) { throw new ArgumentOutOfRangeException("ExpectedOwnedPropertyCount"); } if (!double.IsFinite(WindowDurationGameMinutes) || WindowDurationGameMinutes != 1440.0) { throw new ArgumentOutOfRangeException("WindowDurationGameMinutes"); } ValidateDeadline(DeadlineGameMinutes, Mode); } private static void ValidateDeadline(double? value, Release1KeepTheLightsOffAssignmentMode mode) { if (mode == Release1KeepTheLightsOffAssignmentMode.Recovery) { if (value.HasValue) { throw new ArgumentException("Recovery has no deadline.", "DeadlineGameMinutes"); } } else if (!value.HasValue || !double.IsFinite(value.Value) || value.Value != 4320.0) { throw new ArgumentOutOfRangeException("DeadlineGameMinutes", "Timed Keep the Lights Off stages run for 4320 game minutes."); } } private void ValidateAuthorizationIfPresent() { if (_authorizationCorrelationId.Length != 0) { ValidateAuthorization(_authorizationCorrelationId, Attempt, Mode); } } private static void ValidateAuthorization(string? value, int attempt, Release1KeepTheLightsOffAssignmentMode mode) { Release1TransitionKind release1TransitionKind = mode switch { Release1KeepTheLightsOffAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1KeepTheLightsOffAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1KeepTheLightsOffAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; if (!Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.keep-the-lights-off" || correlation.Attempt != attempt || correlation.TransitionKind != release1TransitionKind) { throw new ArgumentException("Assignment authorization correlation did not match mission, attempt, and mode.", "AuthorizationCorrelationId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", Mode = "); builder.Append(Mode.ToString()); builder.Append(", AuthorizationCorrelationId = "); builder.Append((object?)AuthorizationCorrelationId); builder.Append(", ExpectedOwnedPropertyCount = "); builder.Append(ExpectedOwnedPropertyCount.ToString()); builder.Append(", WindowDurationGameMinutes = "); builder.Append(WindowDurationGameMinutes.ToString()); builder.Append(", DeadlineGameMinutes = "); builder.Append(DeadlineGameMinutes.ToString()); return true; } [CompilerGenerated] private Release1KeepTheLightsOffAssignment(Release1KeepTheLightsOffAssignment original) { _missionKey = original._missionKey; _attempt = original._attempt; _mode = original._mode; _authorizationCorrelationId = original._authorizationCorrelationId; _expectedOwnedPropertyCount = original._expectedOwnedPropertyCount; _windowDurationGameMinutes = original._windowDurationGameMinutes; _deadlineGameMinutes = original._deadlineGameMinutes; } } public sealed record Release1KeepTheLightsOffProgress(string MissionKey, int Attempt, double? ClearConfirmedAtGameMinutes, double? BreachSincePassGameMinutes) { public static Release1KeepTheLightsOffProgress Fresh(int attempt) { return new Release1KeepTheLightsOffProgress("release1.keep-the-lights-off", attempt, null, null); } public void Validate() { if (!string.Equals(MissionKey, "release1.keep-the-lights-off", StringComparison.Ordinal)) { throw new ArgumentException("Progress mission must be Keep the Lights Off.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } ValidateGameMinutes(ClearConfirmedAtGameMinutes, "ClearConfirmedAtGameMinutes"); ValidateGameMinutes(BreachSincePassGameMinutes, "BreachSincePassGameMinutes"); if (BreachSincePassGameMinutes.HasValue && !ClearConfirmedAtGameMinutes.HasValue) { throw new ArgumentException("A breach cannot be recorded before the clear was confirmed.", "BreachSincePassGameMinutes"); } } private static void ValidateGameMinutes(double? value, string parameterName) { if (!value.HasValue || (double.IsFinite(value.Value) && !(value.Value < 0.0))) { return; } throw new ArgumentOutOfRangeException(parameterName, "Game minutes must be finite and non-negative."); } } public static class Release1KeepTheLightsOffAssignmentSelector { public static bool TrySelect(Release1ProductionActivitySnapshot? activity, Release1KeepTheLightsOffAssignmentMode mode, int attempt, string? authorizationCorrelationId, out Release1KeepTheLightsOffAssignment? assignment, out Release1KeepTheLightsOffSelectionStatus status, bool requireAssignedEmployee = true) { assignment = null; status = Release1KeepTheLightsOffSelectionStatus.InvalidInput; if ((object)activity == null) { return false; } try { activity.Validate(); if (activity.Properties.Count < 1) { status = Release1KeepTheLightsOffSelectionStatus.NoOwnedProperty; return false; } if (requireAssignedEmployee && !activity.Properties.Any((Release1ProductionPropertySnapshot property) => property.Employees.Any((Release1ProductionEmployeeSnapshot employee) => !employee.Fired))) { status = Release1KeepTheLightsOffSelectionStatus.NoAssignedEmployee; return false; } assignment = new Release1KeepTheLightsOffAssignment("release1.keep-the-lights-off", attempt, mode, authorizationCorrelationId, activity.Properties.Count, 1440.0, (mode == Release1KeepTheLightsOffAssignmentMode.Recovery) ? ((double?)null) : new double?(4320.0)); status = Release1KeepTheLightsOffSelectionStatus.Selected; return true; } catch (ArgumentException) { assignment = null; status = Release1KeepTheLightsOffSelectionStatus.InvalidInput; return false; } } } public sealed record Release1LogicalCorrelation(string Value, string PlayerId, string MissionKey, int Attempt, Release1TransitionKind TransitionKind, string ReceiptId) { public static Release1LogicalCorrelation Create(string playerId, string missionKey, int attempt, Release1TransitionKind transitionKind, string receiptId) { return ParseOrThrow($"oc10/v1/{playerId}/{missionKey}/{attempt}/{transitionKind}/{receiptId}"); } public static bool TryParse(string? value, out Release1LogicalCorrelation correlation) { correlation = null; if (string.IsNullOrEmpty(value)) { return false; } string[] array = value.Split('/'); if (array.Length != 7 || array[0] != "oc10" || array[1] != "v1") { return false; } if (!int.TryParse(array[4], out var result)) { return false; } if (!Enum.TryParse(array[5], ignoreCase: false, out var result2) || !Enum.IsDefined(result2)) { return false; } if (!IsValidPart(array[2], 128) || !IsValidPart(array[3], 128) || !IsValidPart(array[6], 256)) { return false; } if (array[3] != "release1.intro" && !Release1MissionCatalog.IsEffectScopeKey(array[3])) { return false; } if ((array[3] == "release1.intro") ? ((byte)result != 0) : (result < 1)) { return false; } try { Release1StoryState.ValidatePlayerId(array[2]); correlation = new Release1LogicalCorrelation(value, array[2], array[3], result, result2, array[6]); return true; } catch (ArgumentException) { return false; } } private static bool IsValidPart(string value, int maxLength) { if (value.Length > 0 && value.Length <= maxLength && !value.Any(char.IsControl)) { return !value.Any(char.IsWhiteSpace); } return false; } private static Release1LogicalCorrelation ParseOrThrow(string value) { if (!TryParse(value, out Release1LogicalCorrelation correlation)) { throw new ArgumentException("Correlation is not canonical.", "value"); } return correlation; } } public sealed record Release1MissionDefinition(int Sequence, int Chapter, string MissionKey, string Title, string Archetype); public static class Release1MissionCatalog { public const string IntroScopeKey = "release1.intro"; public const string SmallCourtesy = "release1.small-courtesy"; public const string WrongAddress = "release1.wrong-address"; public const string RoomWithNoName = "release1.room-with-no-name"; public const string ShortNotice = "release1.short-notice"; public const string KeepTheLightsOff = "release1.keep-the-lights-off"; public const string TheEnvelope = "release1.the-envelope"; public const string ChiefCampbell = "release1.chief-campbell"; public static IReadOnlyList All { get; } = new Release1MissionDefinition[6] { new Release1MissionDefinition(1, 1, "release1.small-courtesy", "Small Courtesy", "DeadDrop"), new Release1MissionDefinition(2, 1, "release1.wrong-address", "Wrong Address", "PackageRecovery"), new Release1MissionDefinition(3, 1, "release1.room-with-no-name", "A Room With No Name", "TemporaryStorageSafehouse"), new Release1MissionDefinition(4, 2, "release1.short-notice", "Short Notice", "EmergencySupply"), new Release1MissionDefinition(5, 2, "release1.keep-the-lights-off", "Keep the Lights Off", "OperationalShutdown"), new Release1MissionDefinition(6, 2, "release1.the-envelope", "The Envelope", "CashMovement") }; public static bool IsMissionKey(string? key) { if (key != null) { return All.Any((Release1MissionDefinition mission) => string.Equals(mission.MissionKey, key, StringComparison.Ordinal)); } return false; } public static bool IsEffectScopeKey(string? key) { if (!IsMissionKey(key)) { return string.Equals(key, "release1.chief-campbell", StringComparison.Ordinal); } return true; } public static int IndexOf(string key) { return All.ToList().FindIndex((Release1MissionDefinition mission) => string.Equals(mission.MissionKey, key, StringComparison.Ordinal)); } public static bool AllowsRevertTolerantEffects(string? missionKey) { if (!string.Equals(missionKey, "release1.wrong-address", StringComparison.Ordinal) && !string.Equals(missionKey, "release1.room-with-no-name", StringComparison.Ordinal) && !string.Equals(missionKey, "release1.short-notice", StringComparison.Ordinal) && !string.Equals(missionKey, "release1.keep-the-lights-off", StringComparison.Ordinal) && !string.Equals(missionKey, "release1.the-envelope", StringComparison.Ordinal)) { return string.Equals(missionKey, "release1.chief-campbell", StringComparison.Ordinal); } return true; } } public enum Release1NativeEffectPhase { Prepared, Applied, Committed } public sealed record Release1NativeEffectJournalEntry { public string EffectId { get; init; } public string MissionKey { get; init; } public int Attempt { get; init; } public string EffectKind { get; init; } public string SourceIdentity { get; init; } public string DestinationIdentity { get; init; } public string AmountOrCargoIdentity { get; init; } public Release1NativeEffectPhase Phase { get; init; } public string? NativeReceiptId { get; init; } public long PreparedStoryRevision { get; init; } public string? CommittedStoryCorrelationId { get; init; } public string? AuthorizedStoryCorrelationId { get; init; } public bool ExecutionBlocked { get; init; } public long AuthorizedMissionRevision { get; init; } public Release1NativeEffectJournalEntry(string EffectId, string MissionKey, int Attempt, string EffectKind, string SourceIdentity, string DestinationIdentity, string AmountOrCargoIdentity, Release1NativeEffectPhase Phase, string? NativeReceiptId, long PreparedStoryRevision, string? CommittedStoryCorrelationId = null, string? AuthorizedStoryCorrelationId = null, bool ExecutionBlocked = false, long AuthorizedMissionRevision = -1L) { this.EffectId = EffectId; this.MissionKey = MissionKey; this.Attempt = Attempt; this.EffectKind = EffectKind; this.SourceIdentity = SourceIdentity; this.DestinationIdentity = DestinationIdentity; this.AmountOrCargoIdentity = AmountOrCargoIdentity; this.Phase = Phase; this.NativeReceiptId = NativeReceiptId; this.PreparedStoryRevision = PreparedStoryRevision; this.CommittedStoryCorrelationId = CommittedStoryCorrelationId; this.AuthorizedStoryCorrelationId = AuthorizedStoryCorrelationId; this.ExecutionBlocked = ExecutionBlocked; this.AuthorizedMissionRevision = AuthorizedMissionRevision; Validate(); } public void Validate() { Release1MissionRecord.ValidateId(EffectId, "EffectId"); if (!Release1MissionCatalog.IsEffectScopeKey(MissionKey) || Attempt < 1 || PreparedStoryRevision < 0 || AuthorizedMissionRevision < -1 || !Enum.IsDefined(Phase) || (ExecutionBlocked && Phase != Release1NativeEffectPhase.Prepared)) { throw new ArgumentException("Native effect metadata is invalid."); } Release1MissionRecord.ValidateId(EffectKind, "EffectKind"); Release1MissionRecord.ValidateId(SourceIdentity, "SourceIdentity"); Release1MissionRecord.ValidateId(DestinationIdentity, "DestinationIdentity"); Release1MissionRecord.ValidateId(AmountOrCargoIdentity, "AmountOrCargoIdentity"); if (Phase != Release1NativeEffectPhase.Prepared && string.IsNullOrWhiteSpace(NativeReceiptId)) { throw new ArgumentException("Applied effects require a native receipt.", "NativeReceiptId"); } if (Phase == Release1NativeEffectPhase.Prepared && NativeReceiptId != null) { throw new ArgumentException("Prepared effects cannot contain a native receipt.", "NativeReceiptId"); } if (NativeReceiptId != null) { Release1MissionRecord.ValidateId(NativeReceiptId, "NativeReceiptId"); } if (Phase == Release1NativeEffectPhase.Committed && string.IsNullOrWhiteSpace(CommittedStoryCorrelationId)) { throw new ArgumentException("Committed effects require the accepted story correlation.", "CommittedStoryCorrelationId"); } if (Phase != Release1NativeEffectPhase.Committed && CommittedStoryCorrelationId != null) { throw new ArgumentException("Only committed effects can contain a committed story correlation.", "CommittedStoryCorrelationId"); } if (CommittedStoryCorrelationId != null && !Release1LogicalCorrelation.TryParse(CommittedStoryCorrelationId, out Release1LogicalCorrelation correlation)) { throw new ArgumentException("Committed story correlation was not canonical.", "CommittedStoryCorrelationId"); } if (AuthorizedStoryCorrelationId != null && !Release1LogicalCorrelation.TryParse(AuthorizedStoryCorrelationId, out correlation)) { throw new ArgumentException("Authorized story correlation was not canonical.", "AuthorizedStoryCorrelationId"); } } public bool ValueEquals(Release1NativeEffectJournalEntry? other) { if ((object)other != null) { return Equals(other); } return false; } public bool Equals(Release1NativeEffectJournalEntry? other) { if ((object)other != null && EffectId == other.EffectId && MissionKey == other.MissionKey && Attempt == other.Attempt && EffectKind == other.EffectKind && SourceIdentity == other.SourceIdentity && DestinationIdentity == other.DestinationIdentity && AmountOrCargoIdentity == other.AmountOrCargoIdentity && Phase == other.Phase && NativeReceiptId == other.NativeReceiptId && PreparedStoryRevision == other.PreparedStoryRevision && CommittedStoryCorrelationId == other.CommittedStoryCorrelationId && AuthorizedStoryCorrelationId == other.AuthorizedStoryCorrelationId && ExecutionBlocked == other.ExecutionBlocked) { return AuthorizedMissionRevision == other.AuthorizedMissionRevision; } return false; } public override int GetHashCode() { HashCode hashCode = default(HashCode); hashCode.Add(EffectId); hashCode.Add(MissionKey); hashCode.Add(Attempt); hashCode.Add(EffectKind); hashCode.Add(SourceIdentity); hashCode.Add(DestinationIdentity); hashCode.Add(AmountOrCargoIdentity); hashCode.Add(Phase); hashCode.Add(NativeReceiptId); hashCode.Add(PreparedStoryRevision); hashCode.Add(CommittedStoryCorrelationId); hashCode.Add(AuthorizedStoryCorrelationId); hashCode.Add(ExecutionBlocked); hashCode.Add(AuthorizedMissionRevision); return hashCode.ToHashCode(); } } public enum Release1NativeEffectCommandKind { Prepare, MarkApplied, Commit } public sealed record Release1NativeEffectCommand(Release1NativeEffectCommandKind Kind, string EffectId, string MissionKey, int Attempt, string EffectKind, string SourceIdentity, string DestinationIdentity, string AmountOrCargoIdentity, string? NativeReceiptId = null, string? StoryCorrelationId = null, long AuthorizedMissionRevision = -1L); public enum Release1NativeEffectTransitionRejectReason { None, InvalidCommand, InvalidEffect, DuplicateConflict, WrongPhase, MissingNativeReceipt, MissingStoryCorrelation } public sealed record Release1NativeEffectTransitionResult(bool Accepted, bool Idempotent, IReadOnlyList Effects, Release1NativeEffectTransitionRejectReason RejectReason, string Message) { public static Release1NativeEffectTransitionResult Reject(Release1NativeEffectTransitionRejectReason reason, string message, IReadOnlyList effects) { return new Release1NativeEffectTransitionResult(Accepted: false, Idempotent: false, effects, reason, message); } } public static class Release1NativeEffectJournal { public static Release1NativeEffectTransitionResult Apply(IReadOnlyList effects, Release1NativeEffectCommand command, long storyRevision) { if (effects == null) { effects = Array.Empty(); } if ((object)command == null || !Enum.IsDefined(command.Kind)) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.InvalidCommand, "Native effect command kind was not defined.", effects); } Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = effects.FirstOrDefault((Release1NativeEffectJournalEntry e) => e.EffectId == command.EffectId); try { if (command.Kind == Release1NativeEffectCommandKind.Prepare) { Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = new Release1NativeEffectJournalEntry(command.EffectId, command.MissionKey, command.Attempt, command.EffectKind, command.SourceIdentity, command.DestinationIdentity, command.AmountOrCargoIdentity, Release1NativeEffectPhase.Prepared, null, storyRevision, null, command.StoryCorrelationId, ExecutionBlocked: false, command.AuthorizedMissionRevision); if ((object)release1NativeEffectJournalEntry != null) { bool flag = release1NativeEffectJournalEntry.EffectId == release1NativeEffectJournalEntry2.EffectId && release1NativeEffectJournalEntry.MissionKey == release1NativeEffectJournalEntry2.MissionKey && release1NativeEffectJournalEntry.Attempt == release1NativeEffectJournalEntry2.Attempt && release1NativeEffectJournalEntry.EffectKind == release1NativeEffectJournalEntry2.EffectKind && release1NativeEffectJournalEntry.SourceIdentity == release1NativeEffectJournalEntry2.SourceIdentity && release1NativeEffectJournalEntry.DestinationIdentity == release1NativeEffectJournalEntry2.DestinationIdentity && release1NativeEffectJournalEntry.AmountOrCargoIdentity == release1NativeEffectJournalEntry2.AmountOrCargoIdentity && release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId == release1NativeEffectJournalEntry2.AuthorizedStoryCorrelationId && release1NativeEffectJournalEntry.AuthorizedMissionRevision == release1NativeEffectJournalEntry2.AuthorizedMissionRevision; if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared && flag) { return new Release1NativeEffectTransitionResult(Accepted: true, Idempotent: true, effects, Release1NativeEffectTransitionRejectReason.None, "Duplicate prepare was idempotent."); } return Release1NativeEffectTransitionResult.Reject((release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Prepared) ? Release1NativeEffectTransitionRejectReason.DuplicateConflict : Release1NativeEffectTransitionRejectReason.WrongPhase, "Effect ID already has different or finalized metadata.", effects); } return new Release1NativeEffectTransitionResult(Accepted: true, Idempotent: false, effects.Append(release1NativeEffectJournalEntry2).ToArray(), Release1NativeEffectTransitionRejectReason.None, "Effect prepared."); } if ((object)release1NativeEffectJournalEntry == null) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.InvalidEffect, "Effect was not prepared.", effects); } if (command.Kind == Release1NativeEffectCommandKind.MarkApplied) { if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Applied && release1NativeEffectJournalEntry.NativeReceiptId == command.NativeReceiptId) { return new Release1NativeEffectTransitionResult(Accepted: true, Idempotent: true, effects, Release1NativeEffectTransitionRejectReason.None, "Duplicate apply was idempotent."); } if (release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Prepared) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.WrongPhase, "Only Prepared effects may be applied.", effects); } if (string.IsNullOrWhiteSpace(command.NativeReceiptId)) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.MissingNativeReceipt, "Native receipt is required.", effects); } Release1NativeEffectJournalEntry applied = release1NativeEffectJournalEntry with { Phase = Release1NativeEffectPhase.Applied, NativeReceiptId = command.NativeReceiptId, ExecutionBlocked = false }; return new Release1NativeEffectTransitionResult(Accepted: true, Idempotent: false, effects.Select((Release1NativeEffectJournalEntry e) => (!(e.EffectId == command.EffectId)) ? e : applied).ToArray(), Release1NativeEffectTransitionRejectReason.None, "Effect marked applied."); } if (release1NativeEffectJournalEntry.Phase == Release1NativeEffectPhase.Committed) { if (release1NativeEffectJournalEntry.NativeReceiptId == command.NativeReceiptId && release1NativeEffectJournalEntry.CommittedStoryCorrelationId == command.StoryCorrelationId) { return new Release1NativeEffectTransitionResult(Accepted: true, Idempotent: true, effects, Release1NativeEffectTransitionRejectReason.None, "Duplicate commit was idempotent."); } return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.DuplicateConflict, "Committed effect metadata cannot be changed.", effects); } if (release1NativeEffectJournalEntry.Phase != Release1NativeEffectPhase.Applied) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.WrongPhase, "Only Applied effects may be committed.", effects); } if (release1NativeEffectJournalEntry.NativeReceiptId != command.NativeReceiptId) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.DuplicateConflict, "Native receipt did not match the applied effect.", effects); } if (string.IsNullOrWhiteSpace(command.StoryCorrelationId)) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.MissingStoryCorrelation, "Story correlation is required.", effects); } if (!Release1LogicalCorrelation.TryParse(command.StoryCorrelationId, out Release1LogicalCorrelation correlation) || correlation.MissionKey != release1NativeEffectJournalEntry.MissionKey || correlation.Attempt != release1NativeEffectJournalEntry.Attempt) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.MissingStoryCorrelation, "Story correlation did not match the effect.", effects); } if (release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId == null || release1NativeEffectJournalEntry.AuthorizedStoryCorrelationId != command.StoryCorrelationId) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.MissingStoryCorrelation, "Story correlation did not match the effect authorization.", effects); } Release1NativeEffectJournalEntry committed = release1NativeEffectJournalEntry with { Phase = Release1NativeEffectPhase.Committed, CommittedStoryCorrelationId = command.StoryCorrelationId }; return new Release1NativeEffectTransitionResult(Accepted: true, Idempotent: false, effects.Select((Release1NativeEffectJournalEntry e) => (!(e.EffectId == command.EffectId)) ? e : committed).ToArray(), Release1NativeEffectTransitionRejectReason.None, "Effect committed."); } catch (ArgumentException ex) { return Release1NativeEffectTransitionResult.Reject(Release1NativeEffectTransitionRejectReason.InvalidEffect, ex.Message, effects); } } } public enum Release1PhonePresentationAttemptState { Pending, Attempting, Delivered, Ambiguous, Completed } public sealed record Release1PhonePresentationAttempt(string CorrelationId, string MissionKey, int Attempt, string Role, string? RequiredPriorCorrelationId, Release1PhonePresentationAttemptState State, long Revision) { private const string NellRolePrefix = "release1-phone-nell-"; private const string ArthurRolePrefix = "release1-phone-arthur-"; private const string NellPresentationPrefix = "presentation-nell-"; public void Validate() { if (!Release1LogicalCorrelation.TryParse(CorrelationId, out Release1LogicalCorrelation correlation) || correlation.MissionKey != MissionKey || correlation.Attempt != Attempt) { throw new ArgumentException("Phone presentation correlation was not canonical for its attempt.", "CorrelationId"); } if (!Release1MissionCatalog.IsMissionKey(MissionKey) || Attempt < 1) { throw new ArgumentException("Phone presentation mission or attempt was invalid.", "MissionKey"); } string role = Role; if ((!(role == "Nell") && !(role == "Arthur")) || 1 == 0) { throw new ArgumentException("Phone presentation role was invalid.", "Role"); } string value = ((Role == "Nell") ? "release1-phone-nell-" : "release1-phone-arthur-"); if (!correlation.ReceiptId.StartsWith(value, StringComparison.Ordinal)) { throw new ArgumentException("Phone presentation correlation did not carry its role tag.", "CorrelationId"); } if (Role == "Arthur" && string.IsNullOrWhiteSpace(RequiredPriorCorrelationId)) { throw new ArgumentException("Arthur phone presentations require a prior correlation.", "RequiredPriorCorrelationId"); } if (Role == "Nell" && RequiredPriorCorrelationId != null) { throw new ArgumentException("Nell phone presentations cannot have a prior correlation.", "RequiredPriorCorrelationId"); } if (RequiredPriorCorrelationId != null && !Release1LogicalCorrelation.TryParse(RequiredPriorCorrelationId, out Release1LogicalCorrelation _)) { throw new ArgumentException("Phone presentation prerequisite was not canonical.", "RequiredPriorCorrelationId"); } if (RequiredPriorCorrelationId != null) { Release1LogicalCorrelation.TryParse(RequiredPriorCorrelationId, out Release1LogicalCorrelation correlation3); if (correlation3.PlayerId != correlation.PlayerId || correlation3.MissionKey != MissionKey || correlation3.Attempt != Attempt || (!correlation3.ReceiptId.StartsWith("release1-phone-nell-", StringComparison.Ordinal) && !correlation3.ReceiptId.StartsWith("presentation-nell-", StringComparison.Ordinal))) { throw new ArgumentException("Phone presentation prerequisite did not match the attempt.", "RequiredPriorCorrelationId"); } } if (!Enum.IsDefined(State) || Revision < 0) { throw new ArgumentException("Phone presentation attempt state or revision was invalid."); } } [CompilerGenerated] private Release1PhonePresentationAttempt(Release1PhonePresentationAttempt original) { CorrelationId = original.CorrelationId; MissionKey = original.MissionKey; Attempt = original.Attempt; Role = original.Role; RequiredPriorCorrelationId = original.RequiredPriorCorrelationId; State = original.State; Revision = original.Revision; } } public sealed record Release1PresentationReceipt(string CorrelationId, long Revision) { public void Validate() { if (!Release1LogicalCorrelation.TryParse(CorrelationId, out Release1LogicalCorrelation _)) { throw new ArgumentException("Presentation receipt correlation was not canonical.", "CorrelationId"); } if (Revision < 0) { throw new ArgumentException("Presentation receipt revision cannot be negative.", "Revision"); } } } public enum Release1RoomWithNoNameAssignmentMode { Primary, MakeGood, Recovery } public enum Release1RoomWithNoNameSelectionStatus { Selected, NoDiscoveredProduct, InsufficientEmptyDeadDrops, InvalidInput } public sealed record Release1RoomWithNoNameAssignment { public string MissionKey { get { return _missionKey; } init { if (!string.Equals(value, "release1.room-with-no-name", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be A Room With No Name.", "MissionKey"); } _missionKey = value; } } public int Attempt { get { return _attempt; } init { if (value < 1) { throw new ArgumentOutOfRangeException("Attempt"); } _attempt = value; ValidateAuthorizationIfPresent(); } } public Release1RoomWithNoNameAssignmentMode Mode { get { return _mode; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } _mode = value; ValidateAuthorizationIfPresent(); } } public string AuthorizationCorrelationId { get { return _authorizationCorrelationId; } init { ValidateAuthorization(value, Attempt, Mode); _authorizationCorrelationId = value; } } public string ProductId { get { return _productId; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "ProductId"); _productId = value; } } public string ProductName { get { return _productName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "ProductName"); _productName = value; } } public string PackagingId { get { return _packagingId; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "PackagingId"); _packagingId = value; } } public string PackagingName { get { return _packagingName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "PackagingName"); _packagingName = value; } } public int PackageQuantity { get { return _packageQuantity; } init { if (value != 1) { throw new ArgumentOutOfRangeException("PackageQuantity", "Room With No Name consignments are exactly one unit."); } _packageQuantity = value; } } public string SourceDropGuid { get { return _sourceDropGuid; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "SourceDropGuid"); _sourceDropGuid = value; } } public string SourceDropName { get { return _sourceDropName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "SourceDropName"); _sourceDropName = value; } } public string SourceDropDescription { get { return _sourceDropDescription; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 1024, "SourceDropDescription"); _sourceDropDescription = value; } } public double SourceDropX { get { return _sourceDropX; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "SourceDropX"); _sourceDropX = value; } } public double SourceDropY { get { return _sourceDropY; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "SourceDropY"); _sourceDropY = value; } } public double SourceDropZ { get { return _sourceDropZ; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "SourceDropZ"); _sourceDropZ = value; } } public string HandoffDropGuid { get { return _handoffDropGuid; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "HandoffDropGuid"); if (string.Equals(_sourceDropGuid, value, StringComparison.Ordinal)) { throw new ArgumentException("The pick up and the hand off must be different drops.", "HandoffDropGuid"); } _handoffDropGuid = value; } } public string HandoffDropName { get { return _handoffDropName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "HandoffDropName"); _handoffDropName = value; } } public string HandoffDropDescription { get { return _handoffDropDescription; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 1024, "HandoffDropDescription"); _handoffDropDescription = value; } } public double HandoffDropX { get { return _handoffDropX; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropX"); _handoffDropX = value; } } public double HandoffDropY { get { return _handoffDropY; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropY"); _handoffDropY = value; } } public double HandoffDropZ { get { return _handoffDropZ; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropZ"); _handoffDropZ = value; } } public string HoldRoomKey { get { return _holdRoomKey; } init { if (!string.Equals(value, "syndicate-hq", StringComparison.Ordinal)) { throw new ArgumentException("The hold room is always the Syndicate HQ storage room.", "HoldRoomKey"); } _holdRoomKey = value; } } public int ExpectedClosetCount { get { return _expectedClosetCount; } init { if (value != 9) { throw new ArgumentOutOfRangeException("ExpectedClosetCount", "The hold room has exactly nine closets."); } _expectedClosetCount = value; } } public double HoldDurationGameMinutes { get { return _holdDurationGameMinutes; } init { if (!double.IsFinite(value) || value != 1440.0) { throw new ArgumentOutOfRangeException("HoldDurationGameMinutes", "The hold is exactly one in-game day."); } _holdDurationGameMinutes = value; } } public double RewardMultiplier { get { return _rewardMultiplier; } init { if (!double.IsFinite(value) || value != 1.5) { throw new ArgumentOutOfRangeException("RewardMultiplier", "Room With No Name reward multiplier must be 1.5."); } _rewardMultiplier = value; } } public const string SyndicateHqRoomKey = "syndicate-hq"; public const int SyndicateHqClosetCount = 9; public const double OneInGameDayMinutes = 1440.0; public const double RoomRewardMultiplier = 1.5; private string _missionKey = string.Empty; private int _attempt; private Release1RoomWithNoNameAssignmentMode _mode; private string _authorizationCorrelationId = string.Empty; private string _productId = string.Empty; private string _productName = string.Empty; private string _packagingId = string.Empty; private string _packagingName = string.Empty; private int _packageQuantity; private string _sourceDropGuid = string.Empty; private string _sourceDropName = string.Empty; private string _sourceDropDescription = string.Empty; private double _sourceDropX; private double _sourceDropY; private double _sourceDropZ; private string _handoffDropGuid = string.Empty; private string _handoffDropName = string.Empty; private string _handoffDropDescription = string.Empty; private double _handoffDropX; private double _handoffDropY; private double _handoffDropZ; private string _holdRoomKey = string.Empty; private int _expectedClosetCount; private double _holdDurationGameMinutes; private double _rewardMultiplier; public Release1RoomWithNoNameAssignment(string MissionKey, int Attempt, Release1RoomWithNoNameAssignmentMode Mode, string AuthorizationCorrelationId, string ProductId, string ProductName, string PackagingId, string PackagingName, int PackageQuantity, string SourceDropGuid, string SourceDropName, string SourceDropDescription, double SourceDropX, double SourceDropY, double SourceDropZ, string HandoffDropGuid, string HandoffDropName, string HandoffDropDescription, double HandoffDropX, double HandoffDropY, double HandoffDropZ, string HoldRoomKey, int ExpectedClosetCount, double HoldDurationGameMinutes, double RewardMultiplier) { this.MissionKey = MissionKey; this.Attempt = Attempt; this.Mode = Mode; this.AuthorizationCorrelationId = AuthorizationCorrelationId; this.ProductId = ProductId; this.ProductName = ProductName; this.PackagingId = PackagingId; this.PackagingName = PackagingName; this.PackageQuantity = PackageQuantity; this.SourceDropGuid = SourceDropGuid; this.SourceDropName = SourceDropName; this.SourceDropDescription = SourceDropDescription; this.SourceDropX = SourceDropX; this.SourceDropY = SourceDropY; this.SourceDropZ = SourceDropZ; this.HandoffDropGuid = HandoffDropGuid; this.HandoffDropName = HandoffDropName; this.HandoffDropDescription = HandoffDropDescription; this.HandoffDropX = HandoffDropX; this.HandoffDropY = HandoffDropY; this.HandoffDropZ = HandoffDropZ; this.HoldRoomKey = HoldRoomKey; this.ExpectedClosetCount = ExpectedClosetCount; this.HoldDurationGameMinutes = HoldDurationGameMinutes; this.RewardMultiplier = RewardMultiplier; Validate(); } public void Validate() { if (!string.Equals(MissionKey, "release1.room-with-no-name", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be A Room With No Name.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!Enum.IsDefined(Mode)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } ValidateAuthorization(AuthorizationCorrelationId, Attempt, Mode); Release1SmallCourtesyAssignment.ValidateStableId(ProductId, "ProductId"); Release1SmallCourtesyAssignment.ValidatePresentation(ProductName, 256, "ProductName"); Release1SmallCourtesyAssignment.ValidateStableId(PackagingId, "PackagingId"); Release1SmallCourtesyAssignment.ValidatePresentation(PackagingName, 256, "PackagingName"); if (PackageQuantity != 1) { throw new ArgumentOutOfRangeException("PackageQuantity"); } Release1SmallCourtesyAssignment.ValidateStableId(SourceDropGuid, "SourceDropGuid"); Release1SmallCourtesyAssignment.ValidatePresentation(SourceDropName, 256, "SourceDropName"); Release1SmallCourtesyAssignment.ValidatePresentation(SourceDropDescription, 1024, "SourceDropDescription"); Release1SmallCourtesyAssignment.ValidateFinite(SourceDropX, "SourceDropX"); Release1SmallCourtesyAssignment.ValidateFinite(SourceDropY, "SourceDropY"); Release1SmallCourtesyAssignment.ValidateFinite(SourceDropZ, "SourceDropZ"); Release1SmallCourtesyAssignment.ValidateStableId(HandoffDropGuid, "HandoffDropGuid"); Release1SmallCourtesyAssignment.ValidatePresentation(HandoffDropName, 256, "HandoffDropName"); Release1SmallCourtesyAssignment.ValidatePresentation(HandoffDropDescription, 1024, "HandoffDropDescription"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropX, "HandoffDropX"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropY, "HandoffDropY"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropZ, "HandoffDropZ"); if (string.Equals(SourceDropGuid, HandoffDropGuid, StringComparison.Ordinal)) { throw new ArgumentException("The pick up and the hand off must be different drops.", "HandoffDropGuid"); } if (!string.Equals(HoldRoomKey, "syndicate-hq", StringComparison.Ordinal)) { throw new ArgumentException("The hold room is always the Syndicate HQ storage room.", "HoldRoomKey"); } if (ExpectedClosetCount != 9) { throw new ArgumentOutOfRangeException("ExpectedClosetCount"); } if (!double.IsFinite(HoldDurationGameMinutes) || HoldDurationGameMinutes != 1440.0) { throw new ArgumentOutOfRangeException("HoldDurationGameMinutes"); } if (!double.IsFinite(RewardMultiplier) || RewardMultiplier != 1.5) { throw new ArgumentOutOfRangeException("RewardMultiplier"); } } private void ValidateAuthorizationIfPresent() { if (_authorizationCorrelationId.Length != 0) { ValidateAuthorization(_authorizationCorrelationId, Attempt, Mode); } } private static void ValidateAuthorization(string? value, int attempt, Release1RoomWithNoNameAssignmentMode mode) { Release1TransitionKind release1TransitionKind = mode switch { Release1RoomWithNoNameAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1RoomWithNoNameAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1RoomWithNoNameAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; if (!Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.room-with-no-name" || correlation.Attempt != attempt || correlation.TransitionKind != release1TransitionKind) { throw new ArgumentException("Assignment authorization correlation did not match mission, attempt, and mode.", "AuthorizationCorrelationId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", Mode = "); builder.Append(Mode.ToString()); builder.Append(", AuthorizationCorrelationId = "); builder.Append((object?)AuthorizationCorrelationId); builder.Append(", ProductId = "); builder.Append((object?)ProductId); builder.Append(", ProductName = "); builder.Append((object?)ProductName); builder.Append(", PackagingId = "); builder.Append((object?)PackagingId); builder.Append(", PackagingName = "); builder.Append((object?)PackagingName); builder.Append(", PackageQuantity = "); builder.Append(PackageQuantity.ToString()); builder.Append(", SourceDropGuid = "); builder.Append((object?)SourceDropGuid); builder.Append(", SourceDropName = "); builder.Append((object?)SourceDropName); builder.Append(", SourceDropDescription = "); builder.Append((object?)SourceDropDescription); builder.Append(", SourceDropX = "); builder.Append(SourceDropX.ToString()); builder.Append(", SourceDropY = "); builder.Append(SourceDropY.ToString()); builder.Append(", SourceDropZ = "); builder.Append(SourceDropZ.ToString()); builder.Append(", HandoffDropGuid = "); builder.Append((object?)HandoffDropGuid); builder.Append(", HandoffDropName = "); builder.Append((object?)HandoffDropName); builder.Append(", HandoffDropDescription = "); builder.Append((object?)HandoffDropDescription); builder.Append(", HandoffDropX = "); builder.Append(HandoffDropX.ToString()); builder.Append(", HandoffDropY = "); builder.Append(HandoffDropY.ToString()); builder.Append(", HandoffDropZ = "); builder.Append(HandoffDropZ.ToString()); builder.Append(", HoldRoomKey = "); builder.Append((object?)HoldRoomKey); builder.Append(", ExpectedClosetCount = "); builder.Append(ExpectedClosetCount.ToString()); builder.Append(", HoldDurationGameMinutes = "); builder.Append(HoldDurationGameMinutes.ToString()); builder.Append(", RewardMultiplier = "); builder.Append(RewardMultiplier.ToString()); return true; } [CompilerGenerated] private Release1RoomWithNoNameAssignment(Release1RoomWithNoNameAssignment original) { _missionKey = original._missionKey; _attempt = original._attempt; _mode = original._mode; _authorizationCorrelationId = original._authorizationCorrelationId; _productId = original._productId; _productName = original._productName; _packagingId = original._packagingId; _packagingName = original._packagingName; _packageQuantity = original._packageQuantity; _sourceDropGuid = original._sourceDropGuid; _sourceDropName = original._sourceDropName; _sourceDropDescription = original._sourceDropDescription; _sourceDropX = original._sourceDropX; _sourceDropY = original._sourceDropY; _sourceDropZ = original._sourceDropZ; _handoffDropGuid = original._handoffDropGuid; _handoffDropName = original._handoffDropName; _handoffDropDescription = original._handoffDropDescription; _handoffDropX = original._handoffDropX; _handoffDropY = original._handoffDropY; _handoffDropZ = original._handoffDropZ; _holdRoomKey = original._holdRoomKey; _expectedClosetCount = original._expectedClosetCount; _holdDurationGameMinutes = original._holdDurationGameMinutes; _rewardMultiplier = original._rewardMultiplier; } } public sealed record Release1RoomWithNoNameProgress(string MissionKey, int Attempt, bool Staged, bool Custody, bool Stowed, bool HoldSatisfied, double? StowedAtGameMinutes, string? HoldingClosetGuid, double? MissingSincePassGameMinutes) { public static Release1RoomWithNoNameProgress Fresh(int attempt) { return new Release1RoomWithNoNameProgress("release1.room-with-no-name", attempt, Staged: false, Custody: false, Stowed: false, HoldSatisfied: false, null, null, null); } public void Validate() { if (!string.Equals(MissionKey, "release1.room-with-no-name", StringComparison.Ordinal)) { throw new ArgumentException("Progress mission must be A Room With No Name.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (Custody && !Staged) { throw new ArgumentException("Custody cannot be recorded before the consignment was staged.", "Custody"); } if (Stowed && !Custody) { throw new ArgumentException("A stow cannot be recorded before custody.", "Stowed"); } if (HoldSatisfied && !Stowed) { throw new ArgumentException("The hold cannot be satisfied before the consignment was stowed.", "HoldSatisfied"); } if (Stowed && !StowedAtGameMinutes.HasValue) { throw new ArgumentException("A stow must record when it was first observed.", "StowedAtGameMinutes"); } if (Stowed && string.IsNullOrWhiteSpace(HoldingClosetGuid)) { throw new ArgumentException("A stow must record the holding closet.", "HoldingClosetGuid"); } if (!Stowed && (StowedAtGameMinutes.HasValue || HoldingClosetGuid != null)) { throw new ArgumentException("Stow details cannot exist before a stow was observed.", "StowedAtGameMinutes"); } if (HoldingClosetGuid != null) { Release1SmallCourtesyAssignment.ValidateStableId(HoldingClosetGuid, "HoldingClosetGuid"); } ValidateGameMinutes(StowedAtGameMinutes, "StowedAtGameMinutes"); ValidateGameMinutes(MissingSincePassGameMinutes, "MissingSincePassGameMinutes"); } private static void ValidateGameMinutes(double? value, string parameterName) { if (!value.HasValue || (double.IsFinite(value.Value) && !(value.Value < 0.0))) { return; } throw new ArgumentOutOfRangeException(parameterName, "Game minutes must be finite and non-negative."); } } public static class Release1RoomWithNoNameAssignmentSelector { public static bool TrySelect(IReadOnlyList? products, IReadOnlyList? drops, Release1RoomWithNoNameAssignmentMode mode, int attempt, string? authorizationCorrelationId, string? packagingId, string? packagingName, out Release1RoomWithNoNameAssignment? assignment, out Release1RoomWithNoNameSelectionStatus status) { assignment = null; status = Release1RoomWithNoNameSelectionStatus.InvalidInput; if (products == null || drops == null) { return false; } try { foreach (Release1SmallCourtesyProductCandidate product in products) { (product ?? throw new ArgumentException("Product candidates cannot be null.")).Validate(); } foreach (Release1SmallCourtesyDropCandidate drop in drops) { (drop ?? throw new ArgumentException("Drop candidates cannot be null.")).Validate(); } Release1SmallCourtesyProductCandidate release1SmallCourtesyProductCandidate = (from candidate in products where candidate.IsDiscovered orderby candidate.AskingPrice descending select candidate).ThenBy((Release1SmallCourtesyProductCandidate candidate) => candidate.ProductId, StringComparer.Ordinal).FirstOrDefault(); if ((object)release1SmallCourtesyProductCandidate == null) { status = Release1RoomWithNoNameSelectionStatus.NoDiscoveredProduct; return false; } if (!Release1DeadDropSelection.TryChoosePair(drops, authorizationCorrelationId, out Release1SmallCourtesyDropCandidate source, out Release1SmallCourtesyDropCandidate handoff)) { status = Release1RoomWithNoNameSelectionStatus.InsufficientEmptyDeadDrops; return false; } assignment = new Release1RoomWithNoNameAssignment("release1.room-with-no-name", attempt, mode, authorizationCorrelationId, release1SmallCourtesyProductCandidate.ProductId, release1SmallCourtesyProductCandidate.ProductName, packagingId, packagingName, 1, source.Guid, source.Name, source.Description, source.X, source.Y, source.Z, handoff.Guid, handoff.Name, handoff.Description, handoff.X, handoff.Y, handoff.Z, "syndicate-hq", 9, 1440.0, 1.5); status = Release1RoomWithNoNameSelectionStatus.Selected; return true; } catch (ArgumentException) { assignment = null; status = Release1RoomWithNoNameSelectionStatus.InvalidInput; return false; } } } public enum Release1ShortNoticeAssignmentMode { Primary, MakeGood, Recovery } public enum Release1ShortNoticeSelectionStatus { Selected, NoDiscoveredProduct, NoEmptyDeadDrop, InvalidInput } public sealed record Release1ShortNoticeAssignment { public string MissionKey { get { return _missionKey; } init { if (!string.Equals(value, "release1.short-notice", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Short Notice.", "MissionKey"); } _missionKey = value; } } public int Attempt { get { return _attempt; } init { if (value < 1) { throw new ArgumentOutOfRangeException("Attempt"); } _attempt = value; ValidateAuthorizationIfPresent(); } } public Release1ShortNoticeAssignmentMode Mode { get { return _mode; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } _mode = value; ValidateAuthorizationIfPresent(); } } public string AuthorizationCorrelationId { get { return _authorizationCorrelationId; } init { ValidateAuthorization(value, Attempt, Mode); _authorizationCorrelationId = value; } } public string ProductId { get { return _productId; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "ProductId"); _productId = value; } } public string ProductName { get { return _productName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "ProductName"); _productName = value; } } public string PackagingId { get { return _packagingId; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "PackagingId"); _packagingId = value; } } public string PackagingName { get { return _packagingName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "PackagingName"); _packagingName = value; } } public int RequiredQuantity { get { return _requiredQuantity; } init { if (value != QuantityFor(Mode)) { throw new ArgumentOutOfRangeException("RequiredQuantity", "Short Notice quantity must match the stage."); } _requiredQuantity = value; } } public string HandoffDropGuid { get { return _handoffDropGuid; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "HandoffDropGuid"); _handoffDropGuid = value; } } public string HandoffDropName { get { return _handoffDropName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "HandoffDropName"); _handoffDropName = value; } } public string HandoffDropDescription { get { return _handoffDropDescription; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 1024, "HandoffDropDescription"); _handoffDropDescription = value; } } public double HandoffDropX { get { return _handoffDropX; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropX"); _handoffDropX = value; } } public double HandoffDropY { get { return _handoffDropY; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropY"); _handoffDropY = value; } } public double HandoffDropZ { get { return _handoffDropZ; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropZ"); _handoffDropZ = value; } } public double? DeadlineGameMinutes { get { return _deadlineGameMinutes; } init { ValidateDeadline(value, Mode); _deadlineGameMinutes = value; } } public double RewardMultiplier { get { return _rewardMultiplier; } init { if (!double.IsFinite(value) || value != 1.75) { throw new ArgumentOutOfRangeException("RewardMultiplier", "Short Notice reward multiplier must be 1.75."); } _rewardMultiplier = value; } } public Release1ShortNoticeValueConvention ValueConvention { get { return _valueConvention; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Value convention is not defined.", "ValueConvention"); } _valueConvention = value; } } public const int PrimaryQuantity = 3; public const int MakeGoodQuantity = 2; public const int RecoveryQuantity = 1; public const double TimedStageGameMinutes = 720.0; public const double ShortNoticeRewardMultiplier = 1.75; private string _missionKey = string.Empty; private int _attempt; private Release1ShortNoticeAssignmentMode _mode; private string _authorizationCorrelationId = string.Empty; private string _productId = string.Empty; private string _productName = string.Empty; private string _packagingId = string.Empty; private string _packagingName = string.Empty; private int _requiredQuantity; private string _handoffDropGuid = string.Empty; private string _handoffDropName = string.Empty; private string _handoffDropDescription = string.Empty; private double _handoffDropX; private double _handoffDropY; private double _handoffDropZ; private double? _deadlineGameMinutes; private double _rewardMultiplier; private Release1ShortNoticeValueConvention _valueConvention; public Release1ShortNoticeAssignment(string MissionKey, int Attempt, Release1ShortNoticeAssignmentMode Mode, string AuthorizationCorrelationId, string ProductId, string ProductName, string PackagingId, string PackagingName, int RequiredQuantity, string HandoffDropGuid, string HandoffDropName, string HandoffDropDescription, double HandoffDropX, double HandoffDropY, double HandoffDropZ, double? DeadlineGameMinutes, double RewardMultiplier, Release1ShortNoticeValueConvention ValueConvention) { this.MissionKey = MissionKey; this.Attempt = Attempt; this.Mode = Mode; this.AuthorizationCorrelationId = AuthorizationCorrelationId; this.ProductId = ProductId; this.ProductName = ProductName; this.PackagingId = PackagingId; this.PackagingName = PackagingName; this.RequiredQuantity = RequiredQuantity; this.HandoffDropGuid = HandoffDropGuid; this.HandoffDropName = HandoffDropName; this.HandoffDropDescription = HandoffDropDescription; this.HandoffDropX = HandoffDropX; this.HandoffDropY = HandoffDropY; this.HandoffDropZ = HandoffDropZ; this.DeadlineGameMinutes = DeadlineGameMinutes; this.RewardMultiplier = RewardMultiplier; this.ValueConvention = ValueConvention; Validate(); } public static int QuantityFor(Release1ShortNoticeAssignmentMode mode) { return mode switch { Release1ShortNoticeAssignmentMode.Primary => 3, Release1ShortNoticeAssignmentMode.MakeGood => 2, Release1ShortNoticeAssignmentMode.Recovery => 1, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; } public void Validate() { if (!string.Equals(MissionKey, "release1.short-notice", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Short Notice.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!Enum.IsDefined(Mode)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } ValidateAuthorization(AuthorizationCorrelationId, Attempt, Mode); Release1SmallCourtesyAssignment.ValidateStableId(ProductId, "ProductId"); Release1SmallCourtesyAssignment.ValidatePresentation(ProductName, 256, "ProductName"); Release1SmallCourtesyAssignment.ValidateStableId(PackagingId, "PackagingId"); Release1SmallCourtesyAssignment.ValidatePresentation(PackagingName, 256, "PackagingName"); if (RequiredQuantity != QuantityFor(Mode)) { throw new ArgumentOutOfRangeException("RequiredQuantity"); } Release1SmallCourtesyAssignment.ValidateStableId(HandoffDropGuid, "HandoffDropGuid"); Release1SmallCourtesyAssignment.ValidatePresentation(HandoffDropName, 256, "HandoffDropName"); Release1SmallCourtesyAssignment.ValidatePresentation(HandoffDropDescription, 1024, "HandoffDropDescription"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropX, "HandoffDropX"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropY, "HandoffDropY"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropZ, "HandoffDropZ"); ValidateDeadline(DeadlineGameMinutes, Mode); if (!double.IsFinite(RewardMultiplier) || RewardMultiplier != 1.75) { throw new ArgumentOutOfRangeException("RewardMultiplier"); } if (!Enum.IsDefined(ValueConvention)) { throw new ArgumentException("Value convention is not defined.", "ValueConvention"); } } private static void ValidateDeadline(double? value, Release1ShortNoticeAssignmentMode mode) { if (mode == Release1ShortNoticeAssignmentMode.Recovery) { if (value.HasValue) { throw new ArgumentException("Recovery has no deadline.", "DeadlineGameMinutes"); } } else if (!value.HasValue || !double.IsFinite(value.Value) || value.Value != 720.0) { throw new ArgumentOutOfRangeException("DeadlineGameMinutes", "Timed Short Notice stages run for 720 game minutes."); } } private void ValidateAuthorizationIfPresent() { if (_authorizationCorrelationId.Length != 0) { ValidateAuthorization(_authorizationCorrelationId, Attempt, Mode); } } private static void ValidateAuthorization(string? value, int attempt, Release1ShortNoticeAssignmentMode mode) { Release1TransitionKind release1TransitionKind = mode switch { Release1ShortNoticeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1ShortNoticeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1ShortNoticeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; if (!Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.short-notice" || correlation.Attempt != attempt || correlation.TransitionKind != release1TransitionKind) { throw new ArgumentException("Assignment authorization correlation did not match mission, attempt, and mode.", "AuthorizationCorrelationId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", Mode = "); builder.Append(Mode.ToString()); builder.Append(", AuthorizationCorrelationId = "); builder.Append((object?)AuthorizationCorrelationId); builder.Append(", ProductId = "); builder.Append((object?)ProductId); builder.Append(", ProductName = "); builder.Append((object?)ProductName); builder.Append(", PackagingId = "); builder.Append((object?)PackagingId); builder.Append(", PackagingName = "); builder.Append((object?)PackagingName); builder.Append(", RequiredQuantity = "); builder.Append(RequiredQuantity.ToString()); builder.Append(", HandoffDropGuid = "); builder.Append((object?)HandoffDropGuid); builder.Append(", HandoffDropName = "); builder.Append((object?)HandoffDropName); builder.Append(", HandoffDropDescription = "); builder.Append((object?)HandoffDropDescription); builder.Append(", HandoffDropX = "); builder.Append(HandoffDropX.ToString()); builder.Append(", HandoffDropY = "); builder.Append(HandoffDropY.ToString()); builder.Append(", HandoffDropZ = "); builder.Append(HandoffDropZ.ToString()); builder.Append(", DeadlineGameMinutes = "); builder.Append(DeadlineGameMinutes.ToString()); builder.Append(", RewardMultiplier = "); builder.Append(RewardMultiplier.ToString()); builder.Append(", ValueConvention = "); builder.Append(ValueConvention.ToString()); return true; } [CompilerGenerated] private Release1ShortNoticeAssignment(Release1ShortNoticeAssignment original) { _missionKey = original._missionKey; _attempt = original._attempt; _mode = original._mode; _authorizationCorrelationId = original._authorizationCorrelationId; _productId = original._productId; _productName = original._productName; _packagingId = original._packagingId; _packagingName = original._packagingName; _requiredQuantity = original._requiredQuantity; _handoffDropGuid = original._handoffDropGuid; _handoffDropName = original._handoffDropName; _handoffDropDescription = original._handoffDropDescription; _handoffDropX = original._handoffDropX; _handoffDropY = original._handoffDropY; _handoffDropZ = original._handoffDropZ; _deadlineGameMinutes = original._deadlineGameMinutes; _rewardMultiplier = original._rewardMultiplier; _valueConvention = original._valueConvention; } } public sealed record Release1ShortNoticeProgress(string MissionKey, int Attempt, int ObservedQuantity, int? LastShortfallNoticed, bool SpreadNoticed) { public static Release1ShortNoticeProgress Fresh(int attempt) { return new Release1ShortNoticeProgress("release1.short-notice", attempt, 0, null, SpreadNoticed: false); } public void Validate() { if (!string.Equals(MissionKey, "release1.short-notice", StringComparison.Ordinal)) { throw new ArgumentException("Progress mission must be Short Notice.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (ObservedQuantity < 0) { throw new ArgumentOutOfRangeException("ObservedQuantity"); } if (LastShortfallNoticed.HasValue && LastShortfallNoticed.Value < 1) { throw new ArgumentOutOfRangeException("LastShortfallNoticed", "A noticed shortfall is at least one unit."); } } } public static class Release1ShortNoticeAssignmentSelector { public static bool TrySelect(IReadOnlyList? products, IReadOnlyList? drops, Release1ShortNoticeAssignmentMode mode, int attempt, string? authorizationCorrelationId, string? packagingId, string? packagingName, Release1ShortNoticeValueConvention valueConvention, out Release1ShortNoticeAssignment? assignment, out Release1ShortNoticeSelectionStatus status) { assignment = null; status = Release1ShortNoticeSelectionStatus.InvalidInput; if (products == null || drops == null) { return false; } try { foreach (Release1SmallCourtesyProductCandidate product in products) { (product ?? throw new ArgumentException("Product candidates cannot be null.")).Validate(); } foreach (Release1SmallCourtesyDropCandidate drop in drops) { (drop ?? throw new ArgumentException("Drop candidates cannot be null.")).Validate(); } Release1SmallCourtesyProductCandidate release1SmallCourtesyProductCandidate = (from candidate in products where candidate.IsDiscovered orderby candidate.AskingPrice descending select candidate).ThenBy((Release1SmallCourtesyProductCandidate candidate) => candidate.ProductId, StringComparer.Ordinal).FirstOrDefault(); if ((object)release1SmallCourtesyProductCandidate == null) { status = Release1ShortNoticeSelectionStatus.NoDiscoveredProduct; return false; } if (!Release1DeadDropSelection.TryChooseSingle(drops, authorizationCorrelationId, out Release1SmallCourtesyDropCandidate chosen)) { status = Release1ShortNoticeSelectionStatus.NoEmptyDeadDrop; return false; } assignment = new Release1ShortNoticeAssignment("release1.short-notice", attempt, mode, authorizationCorrelationId, release1SmallCourtesyProductCandidate.ProductId, release1SmallCourtesyProductCandidate.ProductName, packagingId, packagingName, Release1ShortNoticeAssignment.QuantityFor(mode), chosen.Guid, chosen.Name, chosen.Description, chosen.X, chosen.Y, chosen.Z, (mode == Release1ShortNoticeAssignmentMode.Recovery) ? ((double?)null) : new double?(720.0), 1.75, valueConvention); status = Release1ShortNoticeSelectionStatus.Selected; return true; } catch (ArgumentException) { assignment = null; status = Release1ShortNoticeSelectionStatus.InvalidInput; return false; } } } public enum Release1SmallCourtesyAssignmentMode { Primary, MakeGood, Recovery } public enum Release1SmallCourtesyAssignmentSelectionStatus { Selected, NoDiscoveredProduct, NoEmptyDeadDrop, InvalidInput } public sealed record Release1SmallCourtesyProductCandidate(string ProductId, string ProductName, double AskingPrice, bool IsDiscovered) { public void Validate() { Release1SmallCourtesyAssignment.ValidateStableId(ProductId, "ProductId"); Release1SmallCourtesyAssignment.ValidatePresentation(ProductName, 256, "ProductName"); Release1SmallCourtesyAssignment.ValidateNonNegativeFinite(AskingPrice, "AskingPrice"); } } public sealed record Release1SmallCourtesyDropCandidate(string Guid, string Name, string Description, double X, double Y, double Z, bool IsEmpty) { public void Validate() { Release1SmallCourtesyAssignment.ValidateStableId(Guid, "Guid"); Release1SmallCourtesyAssignment.ValidatePresentation(Name, 256, "Name"); Release1SmallCourtesyAssignment.ValidatePresentation(Description, 1024, "Description"); Release1SmallCourtesyAssignment.ValidateFinite(X, "X"); Release1SmallCourtesyAssignment.ValidateFinite(Y, "Y"); Release1SmallCourtesyAssignment.ValidateFinite(Z, "Z"); } } public sealed record Release1SmallCourtesyAssignment { public string MissionKey { get { return _missionKey; } init { if (!string.Equals(value, "release1.small-courtesy", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Small Courtesy.", "MissionKey"); } _missionKey = value; } } public int Attempt { get { return _attempt; } init { if (value < 1) { throw new ArgumentOutOfRangeException("Attempt"); } _attempt = value; ValidateAuthorizationIfPresent(); } } public Release1SmallCourtesyAssignmentMode Mode { get { return _mode; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } _mode = value; ValidateAuthorizationIfPresent(); } } public string AuthorizationCorrelationId { get { return _authorizationCorrelationId; } init { ValidateAuthorization(value, Attempt, Mode); _authorizationCorrelationId = value; } } public string ProductId { get { return _productId; } init { ValidateStableId(value, "ProductId"); _productId = value; } } public string ProductName { get { return _productName; } init { ValidatePresentation(value, 256, "ProductName"); _productName = value; } } public double SelectionAskingPrice { get { return _selectionAskingPrice; } init { ValidateNonNegativeFinite(value, "SelectionAskingPrice"); _selectionAskingPrice = value; } } public string PackagingId { get { return _packagingId; } init { ValidateStableId(value, "PackagingId"); _packagingId = value; } } public string PackagingName { get { return _packagingName; } init { ValidatePresentation(value, 256, "PackagingName"); _packagingName = value; } } public string DeadDropGuid { get { return _deadDropGuid; } init { ValidateStableId(value, "DeadDropGuid"); _deadDropGuid = value; } } public string DeadDropName { get { return _deadDropName; } init { ValidatePresentation(value, 256, "DeadDropName"); _deadDropName = value; } } public string DeadDropDescription { get { return _deadDropDescription; } init { ValidatePresentation(value, 1024, "DeadDropDescription"); _deadDropDescription = value; } } public double DeadDropX { get { return _deadDropX; } init { ValidateFinite(value, "DeadDropX"); _deadDropX = value; } } public double DeadDropY { get { return _deadDropY; } init { ValidateFinite(value, "DeadDropY"); _deadDropY = value; } } public double DeadDropZ { get { return _deadDropZ; } init { ValidateFinite(value, "DeadDropZ"); _deadDropZ = value; } } public double RewardMultiplier { get { return _rewardMultiplier; } init { if (!double.IsFinite(value) || value != 1.25) { throw new ArgumentOutOfRangeException("RewardMultiplier", "Small Courtesy reward multiplier must be 1.25."); } _rewardMultiplier = value; } } private string _missionKey = string.Empty; private int _attempt; private Release1SmallCourtesyAssignmentMode _mode; private string _authorizationCorrelationId = string.Empty; private string _productId = string.Empty; private string _productName = string.Empty; private double _selectionAskingPrice; private string _packagingId = string.Empty; private string _packagingName = string.Empty; private string _deadDropGuid = string.Empty; private string _deadDropName = string.Empty; private string _deadDropDescription = string.Empty; private double _deadDropX; private double _deadDropY; private double _deadDropZ; private double _rewardMultiplier; public Release1SmallCourtesyAssignment(string MissionKey, int Attempt, Release1SmallCourtesyAssignmentMode Mode, string AuthorizationCorrelationId, string ProductId, string ProductName, double SelectionAskingPrice, string PackagingId, string PackagingName, string DeadDropGuid, string DeadDropName, string DeadDropDescription, double DeadDropX, double DeadDropY, double DeadDropZ, double RewardMultiplier) { this.MissionKey = MissionKey; this.Attempt = Attempt; this.Mode = Mode; this.AuthorizationCorrelationId = AuthorizationCorrelationId; this.ProductId = ProductId; this.ProductName = ProductName; this.SelectionAskingPrice = SelectionAskingPrice; this.PackagingId = PackagingId; this.PackagingName = PackagingName; this.DeadDropGuid = DeadDropGuid; this.DeadDropName = DeadDropName; this.DeadDropDescription = DeadDropDescription; this.DeadDropX = DeadDropX; this.DeadDropY = DeadDropY; this.DeadDropZ = DeadDropZ; this.RewardMultiplier = RewardMultiplier; Validate(); } public void Validate() { if (!string.Equals(MissionKey, "release1.small-courtesy", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Small Courtesy.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!Enum.IsDefined(Mode)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } ValidateAuthorization(AuthorizationCorrelationId, Attempt, Mode); ValidateStableId(ProductId, "ProductId"); ValidatePresentation(ProductName, 256, "ProductName"); ValidateNonNegativeFinite(SelectionAskingPrice, "SelectionAskingPrice"); ValidateStableId(PackagingId, "PackagingId"); ValidatePresentation(PackagingName, 256, "PackagingName"); ValidateStableId(DeadDropGuid, "DeadDropGuid"); ValidatePresentation(DeadDropName, 256, "DeadDropName"); ValidatePresentation(DeadDropDescription, 1024, "DeadDropDescription"); ValidateFinite(DeadDropX, "DeadDropX"); ValidateFinite(DeadDropY, "DeadDropY"); ValidateFinite(DeadDropZ, "DeadDropZ"); if (!double.IsFinite(RewardMultiplier) || RewardMultiplier != 1.25) { throw new ArgumentOutOfRangeException("RewardMultiplier"); } } internal static void ValidateStableId(string? value, string parameterName) { if (string.IsNullOrWhiteSpace(value) || value.Length > 256 || value.Any(char.IsControl) || value.Any(char.IsWhiteSpace) || value.Contains('/')) { throw new ArgumentException("Stable IDs must be nonblank, bounded, whitespace-free single segments.", parameterName); } } internal static void ValidatePresentation(string? value, int maximumLength, string parameterName) { if (string.IsNullOrWhiteSpace(value) || value.Length > maximumLength || value.Any(char.IsControl)) { throw new ArgumentException("Presentation text must be nonblank, bounded, and control-character-free.", parameterName); } } internal static void ValidateFinite(double value, string parameterName) { if (!double.IsFinite(value)) { throw new ArgumentOutOfRangeException(parameterName, "Value must be finite."); } } internal static void ValidateNonNegativeFinite(double value, string parameterName) { if (!double.IsFinite(value) || value < 0.0) { throw new ArgumentOutOfRangeException(parameterName, "Value must be finite and non-negative."); } } private void ValidateAuthorizationIfPresent() { if (_authorizationCorrelationId.Length != 0) { ValidateAuthorization(_authorizationCorrelationId, Attempt, Mode); } } private static void ValidateAuthorization(string? value, int attempt, Release1SmallCourtesyAssignmentMode mode) { Release1TransitionKind release1TransitionKind = mode switch { Release1SmallCourtesyAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1SmallCourtesyAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1SmallCourtesyAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; if (!Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.small-courtesy" || correlation.Attempt != attempt || correlation.TransitionKind != release1TransitionKind) { throw new ArgumentException("Assignment authorization correlation did not match mission, attempt, and mode.", "AuthorizationCorrelationId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", Mode = "); builder.Append(Mode.ToString()); builder.Append(", AuthorizationCorrelationId = "); builder.Append((object?)AuthorizationCorrelationId); builder.Append(", ProductId = "); builder.Append((object?)ProductId); builder.Append(", ProductName = "); builder.Append((object?)ProductName); builder.Append(", SelectionAskingPrice = "); builder.Append(SelectionAskingPrice.ToString()); builder.Append(", PackagingId = "); builder.Append((object?)PackagingId); builder.Append(", PackagingName = "); builder.Append((object?)PackagingName); builder.Append(", DeadDropGuid = "); builder.Append((object?)DeadDropGuid); builder.Append(", DeadDropName = "); builder.Append((object?)DeadDropName); builder.Append(", DeadDropDescription = "); builder.Append((object?)DeadDropDescription); builder.Append(", DeadDropX = "); builder.Append(DeadDropX.ToString()); builder.Append(", DeadDropY = "); builder.Append(DeadDropY.ToString()); builder.Append(", DeadDropZ = "); builder.Append(DeadDropZ.ToString()); builder.Append(", RewardMultiplier = "); builder.Append(RewardMultiplier.ToString()); return true; } } public static class Release1SmallCourtesyAssignmentSelector { public static bool TrySelect(IReadOnlyList? products, IReadOnlyList? drops, Release1SmallCourtesyAssignmentMode mode, int attempt, string? authorizationCorrelationId, string? packagingId, string? packagingName, out Release1SmallCourtesyAssignment? assignment, out Release1SmallCourtesyAssignmentSelectionStatus status) { assignment = null; status = Release1SmallCourtesyAssignmentSelectionStatus.InvalidInput; if (products == null || drops == null) { return false; } try { foreach (Release1SmallCourtesyProductCandidate product in products) { (product ?? throw new ArgumentException("Product candidates cannot be null.")).Validate(); } foreach (Release1SmallCourtesyDropCandidate drop in drops) { (drop ?? throw new ArgumentException("Drop candidates cannot be null.")).Validate(); } Release1SmallCourtesyProductCandidate release1SmallCourtesyProductCandidate = (from candidate in products where candidate.IsDiscovered orderby candidate.AskingPrice descending select candidate).ThenBy((Release1SmallCourtesyProductCandidate candidate) => candidate.ProductId, StringComparer.Ordinal).FirstOrDefault(); if ((object)release1SmallCourtesyProductCandidate == null) { status = Release1SmallCourtesyAssignmentSelectionStatus.NoDiscoveredProduct; return false; } Release1SmallCourtesyDropCandidate[] array = drops.Where((Release1SmallCourtesyDropCandidate candidate) => candidate.IsEmpty).OrderBy((Release1SmallCourtesyDropCandidate candidate) => candidate.Guid, StringComparer.Ordinal).ToArray(); if (array.Length == 0) { status = Release1SmallCourtesyAssignmentSelectionStatus.NoEmptyDeadDrop; return false; } int num = (int)(BinaryPrimitives.ReadUInt64BigEndian(SHA256.HashData(Encoding.UTF8.GetBytes(authorizationCorrelationId ?? string.Empty)).AsSpan(0, 8)) % (ulong)array.Length); Release1SmallCourtesyDropCandidate release1SmallCourtesyDropCandidate = array[num]; assignment = new Release1SmallCourtesyAssignment("release1.small-courtesy", attempt, mode, authorizationCorrelationId, release1SmallCourtesyProductCandidate.ProductId, release1SmallCourtesyProductCandidate.ProductName, release1SmallCourtesyProductCandidate.AskingPrice, packagingId, packagingName, release1SmallCourtesyDropCandidate.Guid, release1SmallCourtesyDropCandidate.Name, release1SmallCourtesyDropCandidate.Description, release1SmallCourtesyDropCandidate.X, release1SmallCourtesyDropCandidate.Y, release1SmallCourtesyDropCandidate.Z, 1.25); status = Release1SmallCourtesyAssignmentSelectionStatus.Selected; return true; } catch (ArgumentException) { assignment = null; status = Release1SmallCourtesyAssignmentSelectionStatus.InvalidInput; return false; } } } public sealed record Release1SmallCourtesyCargoIdentity { public string ProductId { get; } public string PackageId { get; } public int SlotIndex { get; } public int PreQuantity { get; } public int PostQuantity { get; } public float DepositedMonetaryValue { get; } public int ConsumedCount => PreQuantity - PostQuantity; private const string Prefix = "sc-cargo-v1"; private const char Delimiter = '|'; private const int MaximumEncodedLength = 256; public Release1SmallCourtesyCargoIdentity(string productId, string packageId, int slotIndex, int preQuantity, int postQuantity, float depositedMonetaryValue) { ValidateId(productId, "productId"); ValidateId(packageId, "packageId"); if (slotIndex < 0) { throw new ArgumentOutOfRangeException("slotIndex"); } if (preQuantity < 1 || postQuantity < 0 || postQuantity >= preQuantity) { throw new ArgumentException("Cargo quantities must describe at least one consumed unit."); } if (!float.IsFinite(depositedMonetaryValue) || depositedMonetaryValue < 0f) { throw new ArgumentOutOfRangeException("depositedMonetaryValue"); } ProductId = productId; PackageId = packageId; SlotIndex = slotIndex; PreQuantity = preQuantity; PostQuantity = postQuantity; DepositedMonetaryValue = depositedMonetaryValue; if (BuildEncoded().Length > 256) { throw new ArgumentException("Cargo identity exceeds the native-effect field limit."); } } public string Serialize() { return BuildEncoded(); } public static bool TryParse(string? encoded, out Release1SmallCourtesyCargoIdentity? identity) { identity = null; if (string.IsNullOrEmpty(encoded) || encoded.Length > 256) { return false; } string[] array = encoded.Split('|'); if (array.Length != 7 || !string.Equals(array[0], "sc-cargo-v1", StringComparison.Ordinal) || !int.TryParse(array[3], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !int.TryParse(array[4], NumberStyles.None, CultureInfo.InvariantCulture, out var result2) || !int.TryParse(array[5], NumberStyles.None, CultureInfo.InvariantCulture, out var result3) || !float.TryParse(array[6], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4)) { return false; } try { identity = new Release1SmallCourtesyCargoIdentity(array[1], array[2], result, result2, result3, result4); return string.Equals(identity.Serialize(), encoded, StringComparison.Ordinal); } catch (ArgumentException) { identity = null; return false; } } private string BuildEncoded() { return string.Join('|', "sc-cargo-v1", ProductId, PackageId, SlotIndex.ToString(CultureInfo.InvariantCulture), PreQuantity.ToString(CultureInfo.InvariantCulture), PostQuantity.ToString(CultureInfo.InvariantCulture), DepositedMonetaryValue.ToString("R", CultureInfo.InvariantCulture)); } private static void ValidateId(string value, string parameterName) { Release1SmallCourtesyAssignment.ValidateStableId(value, parameterName); if (value.Contains('|')) { throw new ArgumentException("Native-effect identity IDs cannot contain the field delimiter.", parameterName); } } [CompilerGenerated] private Release1SmallCourtesyCargoIdentity(Release1SmallCourtesyCargoIdentity original) { ProductId = original.ProductId; PackageId = original.PackageId; SlotIndex = original.SlotIndex; PreQuantity = original.PreQuantity; PostQuantity = original.PostQuantity; DepositedMonetaryValue = original.DepositedMonetaryValue; } } public sealed record Release1SmallCourtesyRewardIdentity { public float BaselineCash { get; } public float WholeDollarAmount { get; } public float ExpectedCash { get; } public float VerificationTolerance => 0.5f; private const string Prefix = "sc-reward-v1"; private const char Delimiter = '|'; private const int MaximumEncodedLength = 256; public const float FixedVerificationTolerance = 0.5f; public Release1SmallCourtesyRewardIdentity(float baselineCash, float wholeDollarAmount, float expectedCash) { ValidateMoney(baselineCash, "baselineCash"); ValidateMoney(wholeDollarAmount, "wholeDollarAmount"); ValidateMoney(expectedCash, "expectedCash"); if (wholeDollarAmount != MathF.Truncate(wholeDollarAmount)) { throw new ArgumentException("Reward amount must be a whole-dollar value.", "wholeDollarAmount"); } float num = baselineCash + wholeDollarAmount; if (!float.IsFinite(num) || MathF.Abs(expectedCash - num) > 0.5f) { throw new ArgumentException("Expected cash does not match baseline plus reward within tolerance.", "expectedCash"); } BaselineCash = baselineCash; WholeDollarAmount = wholeDollarAmount; ExpectedCash = expectedCash; } public string Serialize() { return string.Join('|', "sc-reward-v1", BaselineCash.ToString("R", CultureInfo.InvariantCulture), WholeDollarAmount.ToString("R", CultureInfo.InvariantCulture), ExpectedCash.ToString("R", CultureInfo.InvariantCulture), 0.5f.ToString("R", CultureInfo.InvariantCulture)); } public static bool TryParse(string? encoded, out Release1SmallCourtesyRewardIdentity? identity) { identity = null; if (string.IsNullOrEmpty(encoded) || encoded.Length > 256) { return false; } string[] array = encoded.Split('|'); if (array.Length != 5 || !string.Equals(array[0], "sc-reward-v1", StringComparison.Ordinal) || !float.TryParse(array[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result) || !float.TryParse(array[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || !float.TryParse(array[3], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3) || !float.TryParse(array[4], NumberStyles.Float, CultureInfo.InvariantCulture, out var result4) || result4 != 0.5f) { return false; } try { identity = new Release1SmallCourtesyRewardIdentity(result, result2, result3); return string.Equals(identity.Serialize(), encoded, StringComparison.Ordinal); } catch (ArgumentException) { identity = null; return false; } } private static void ValidateMoney(float value, string parameterName) { if (!float.IsFinite(value) || value < 0f) { throw new ArgumentOutOfRangeException(parameterName, "Money values must be finite and non-negative."); } } [CompilerGenerated] private Release1SmallCourtesyRewardIdentity(Release1SmallCourtesyRewardIdentity original) { BaselineCash = original.BaselineCash; WholeDollarAmount = original.WholeDollarAmount; ExpectedCash = original.ExpectedCash; } } public enum Release1StandingBand { Cold, Tolerated, Useful, Trusted, RegionalProspect } public enum Release1RelationshipState { Unstarted, Deferred, Accepted } public enum Release1MissionState { Locked, Offered, Deferred, Accepted, Active, Abandoned, MakeGoodOffered, MakeGoodActive, RecoveryAvailable, RecoveryActive, Satisfied } public enum Release1MissionOutcome { None, OnTime, Late, Abandoned, RequiredFailure, MakeGoodFailure, RecoveryFailure } public enum Release1CompletionTiming { OnTime, Late } public enum Release1RecoveryMode { None, StandardMakeGood, GuaranteedRecovery } public sealed record Release1MissionRecord { public string MissionKey { get; init; } public Release1MissionState State { get; init; } public int Attempt { get; init; } public string? TermsVersion { get; init; } public double? AcceptedGameTimeHours { get; init; } public double? DeadlineGameTimeHours { get; init; } public Release1MissionOutcome LastOutcome { get; init; } public int StandingPenaltyApplied { get; init; } public IReadOnlyList PenaltyReceiptIds { get { return _penaltyReceiptIds; } init { _penaltyReceiptIds = Freeze(value, "PenaltyReceiptIds"); } } public int MakeGoodFailures { get; init; } public Release1RecoveryMode RecoveryMode { get; init; } public string? RewardAuthorizationReceiptId { get; init; } public bool QuietConditionAwarded { get; init; } public IReadOnlyList NativeEffectIds { get { return _nativeEffectIds; } init { _nativeEffectIds = Freeze(value, "NativeEffectIds"); } } public IReadOnlyList PresentationCorrelationIds { get { return _presentationCorrelationIds; } init { _presentationCorrelationIds = Freeze(value, "PresentationCorrelationIds"); } } public IReadOnlyList AcceptedLogicalCorrelations { get { return _acceptedLogicalCorrelations; } init { _acceptedLogicalCorrelations = Freeze(value, "AcceptedLogicalCorrelations"); } } public string? NativePresentationRef { get; init; } public long Revision { get; init; } private IReadOnlyList _penaltyReceiptIds = ImmutableArray.Empty; private IReadOnlyList _nativeEffectIds = ImmutableArray.Empty; private IReadOnlyList _presentationCorrelationIds = ImmutableArray.Empty; private IReadOnlyList _acceptedLogicalCorrelations = ImmutableArray.Empty; public Release1MissionRecord(string MissionKey, Release1MissionState State, int Attempt, string? TermsVersion, double? AcceptedGameTimeHours, double? DeadlineGameTimeHours, Release1MissionOutcome LastOutcome, int StandingPenaltyApplied, IReadOnlyList PenaltyReceiptIds, int MakeGoodFailures, Release1RecoveryMode RecoveryMode, string? RewardAuthorizationReceiptId, bool QuietConditionAwarded, IReadOnlyList NativeEffectIds, IReadOnlyList PresentationCorrelationIds, IReadOnlyList AcceptedLogicalCorrelations, string? NativePresentationRef, long Revision) { this.MissionKey = MissionKey; this.State = State; this.Attempt = Attempt; this.TermsVersion = TermsVersion; this.AcceptedGameTimeHours = AcceptedGameTimeHours; this.DeadlineGameTimeHours = DeadlineGameTimeHours; this.LastOutcome = LastOutcome; this.StandingPenaltyApplied = StandingPenaltyApplied; this.PenaltyReceiptIds = CopyAndValidateIds(PenaltyReceiptIds, "PenaltyReceiptIds"); this.MakeGoodFailures = MakeGoodFailures; this.RecoveryMode = RecoveryMode; this.RewardAuthorizationReceiptId = RewardAuthorizationReceiptId; this.QuietConditionAwarded = QuietConditionAwarded; this.NativeEffectIds = CopyAndValidateIds(NativeEffectIds, "NativeEffectIds"); this.PresentationCorrelationIds = CopyAndValidateIds(PresentationCorrelationIds, "PresentationCorrelationIds", allowCorrelation: true); this.AcceptedLogicalCorrelations = CopyAndValidateIds(AcceptedLogicalCorrelations, "AcceptedLogicalCorrelations", allowCorrelation: true); this.NativePresentationRef = NativePresentationRef; this.Revision = Revision; Validate(); } public void Validate() { if (!Release1MissionCatalog.IsMissionKey(MissionKey)) { throw new ArgumentException("Mission key is not one of the canonical Release 1 missions.", "MissionKey"); } if (!Enum.IsDefined(State)) { throw new ArgumentException("Mission state is not defined.", "State"); } if (!Enum.IsDefined(LastOutcome)) { throw new ArgumentException("Mission outcome is not defined.", "LastOutcome"); } if (!Enum.IsDefined(RecoveryMode)) { throw new ArgumentException("Recovery mode is not defined.", "RecoveryMode"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt", "Mission attempts must be positive."); } int standingPenaltyApplied = StandingPenaltyApplied; if ((standingPenaltyApplied < 0 || standingPenaltyApplied > 20) ? true : false) { throw new ArgumentOutOfRangeException("StandingPenaltyApplied", "Mission penalty must be between 0 and 20."); } standingPenaltyApplied = MakeGoodFailures; if ((standingPenaltyApplied < 0 || standingPenaltyApplied > 1) ? true : false) { throw new ArgumentOutOfRangeException("MakeGoodFailures", "Only one standard make-good may fail."); } if (Revision < 0) { throw new ArgumentOutOfRangeException("Revision", "Mission revision cannot be negative."); } if (TermsVersion != null && string.IsNullOrWhiteSpace(TermsVersion)) { throw new ArgumentException("Terms version cannot be blank.", "TermsVersion"); } ValidateGameTime(AcceptedGameTimeHours, "AcceptedGameTimeHours"); ValidateGameTime(DeadlineGameTimeHours, "DeadlineGameTimeHours"); if (AcceptedGameTimeHours.HasValue && DeadlineGameTimeHours.HasValue && DeadlineGameTimeHours < AcceptedGameTimeHours) { throw new ArgumentException("Mission deadline cannot precede acceptance.", "DeadlineGameTimeHours"); } if (RewardAuthorizationReceiptId != null) { ValidateId(RewardAuthorizationReceiptId, "RewardAuthorizationReceiptId"); } if (NativePresentationRef != null) { ValidateId(NativePresentationRef, "NativePresentationRef"); } foreach (string item in PresentationCorrelationIds.Concat(AcceptedLogicalCorrelations)) { if (!Release1LogicalCorrelation.TryParse(item, out Release1LogicalCorrelation _)) { throw new ArgumentException("Persisted logical correlations must be canonical."); } } if (State == Release1MissionState.Satisfied && RewardAuthorizationReceiptId == null) { throw new ArgumentException("Satisfied missions require reward authorization.", "State"); } if (State == Release1MissionState.Satisfied && LastOutcome == Release1MissionOutcome.None) { throw new ArgumentException("Satisfied missions require a completion outcome.", "State"); } } public bool ValueEquals(Release1MissionRecord? other) { if ((object)other != null && MissionKey == other.MissionKey && State == other.State && Attempt == other.Attempt && TermsVersion == other.TermsVersion && AcceptedGameTimeHours == other.AcceptedGameTimeHours && DeadlineGameTimeHours == other.DeadlineGameTimeHours && LastOutcome == other.LastOutcome && StandingPenaltyApplied == other.StandingPenaltyApplied && MakeGoodFailures == other.MakeGoodFailures && RecoveryMode == other.RecoveryMode && RewardAuthorizationReceiptId == other.RewardAuthorizationReceiptId && QuietConditionAwarded == other.QuietConditionAwarded && NativePresentationRef == other.NativePresentationRef && Revision == other.Revision && SequenceEqual(PenaltyReceiptIds, other.PenaltyReceiptIds) && SequenceEqual(NativeEffectIds, other.NativeEffectIds) && SequenceEqual(PresentationCorrelationIds, other.PresentationCorrelationIds)) { return SequenceEqual(AcceptedLogicalCorrelations, other.AcceptedLogicalCorrelations); } return false; } public bool Equals(Release1MissionRecord? other) { return ValueEquals(other); } public override int GetHashCode() { HashCode hashCode = default(HashCode); hashCode.Add(MissionKey); hashCode.Add(State); hashCode.Add(Attempt); hashCode.Add(TermsVersion); hashCode.Add(AcceptedGameTimeHours); hashCode.Add(DeadlineGameTimeHours); hashCode.Add(LastOutcome); hashCode.Add(StandingPenaltyApplied); hashCode.Add(MakeGoodFailures); hashCode.Add(RecoveryMode); hashCode.Add(RewardAuthorizationReceiptId); hashCode.Add(QuietConditionAwarded); hashCode.Add(NativePresentationRef); hashCode.Add(Revision); foreach (string penaltyReceiptId in PenaltyReceiptIds) { hashCode.Add(penaltyReceiptId); } foreach (string nativeEffectId in NativeEffectIds) { hashCode.Add(nativeEffectId); } foreach (string presentationCorrelationId in PresentationCorrelationIds) { hashCode.Add(presentationCorrelationId); } foreach (string acceptedLogicalCorrelation in AcceptedLogicalCorrelations) { hashCode.Add(acceptedLogicalCorrelation); } return hashCode.ToHashCode(); } private static IReadOnlyList CopyAndValidateIds(IReadOnlyList? values, string parameterName, bool allowCorrelation = false) { if (values == null) { throw new ArgumentNullException(parameterName); } string[] array = values.ToArray(); string[] array2 = array; foreach (string value in array2) { if (allowCorrelation) { ValidateCorrelation(value, parameterName); } else { ValidateId(value, parameterName); } } if (array.Distinct(StringComparer.Ordinal).Count() != array.Length) { throw new ArgumentException("Collection IDs must be unique.", parameterName); } return array; } internal static void ValidateId(string? value, string parameterName) { if (string.IsNullOrWhiteSpace(value) || value.Any(char.IsControl) || value.Contains('/') || value.Length > 256) { throw new ArgumentException("IDs must be nonblank, bounded, and single-segment.", parameterName); } } internal static void ValidateCorrelation(string? value, string parameterName) { if (string.IsNullOrWhiteSpace(value) || value.Any(char.IsControl) || value.Length > 1024) { throw new ArgumentException("Correlations must be nonblank, bounded, and control-character-free.", parameterName); } } private static IReadOnlyList Freeze(IReadOnlyList? values, string parameterName) { if (values == null) { throw new ArgumentNullException(parameterName); } return ImmutableArray.CreateRange(values); } private static void ValidateGameTime(double? value, string parameterName) { if (value.HasValue && (double.IsNaN(value.Value) || double.IsInfinity(value.Value) || value < 0.0)) { throw new ArgumentOutOfRangeException(parameterName, "Game time must be finite and non-negative."); } } private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList right) { return left.SequenceEqual(right, StringComparer.Ordinal); } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", State = "); builder.Append(State.ToString()); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", TermsVersion = "); builder.Append((object?)TermsVersion); builder.Append(", AcceptedGameTimeHours = "); builder.Append(AcceptedGameTimeHours.ToString()); builder.Append(", DeadlineGameTimeHours = "); builder.Append(DeadlineGameTimeHours.ToString()); builder.Append(", LastOutcome = "); builder.Append(LastOutcome.ToString()); builder.Append(", StandingPenaltyApplied = "); builder.Append(StandingPenaltyApplied.ToString()); builder.Append(", PenaltyReceiptIds = "); builder.Append(PenaltyReceiptIds); builder.Append(", MakeGoodFailures = "); builder.Append(MakeGoodFailures.ToString()); builder.Append(", RecoveryMode = "); builder.Append(RecoveryMode.ToString()); builder.Append(", RewardAuthorizationReceiptId = "); builder.Append((object?)RewardAuthorizationReceiptId); builder.Append(", QuietConditionAwarded = "); builder.Append(QuietConditionAwarded.ToString()); builder.Append(", NativeEffectIds = "); builder.Append(NativeEffectIds); builder.Append(", PresentationCorrelationIds = "); builder.Append(PresentationCorrelationIds); builder.Append(", AcceptedLogicalCorrelations = "); builder.Append(AcceptedLogicalCorrelations); builder.Append(", NativePresentationRef = "); builder.Append((object?)NativePresentationRef); builder.Append(", Revision = "); builder.Append(Revision.ToString()); return true; } [CompilerGenerated] private Release1MissionRecord(Release1MissionRecord original) { MissionKey = original.MissionKey; State = original.State; Attempt = original.Attempt; TermsVersion = original.TermsVersion; AcceptedGameTimeHours = original.AcceptedGameTimeHours; DeadlineGameTimeHours = original.DeadlineGameTimeHours; LastOutcome = original.LastOutcome; StandingPenaltyApplied = original.StandingPenaltyApplied; _penaltyReceiptIds = original._penaltyReceiptIds; MakeGoodFailures = original.MakeGoodFailures; RecoveryMode = original.RecoveryMode; RewardAuthorizationReceiptId = original.RewardAuthorizationReceiptId; QuietConditionAwarded = original.QuietConditionAwarded; _nativeEffectIds = original._nativeEffectIds; _presentationCorrelationIds = original._presentationCorrelationIds; _acceptedLogicalCorrelations = original._acceptedLogicalCorrelations; NativePresentationRef = original.NativePresentationRef; Revision = original.Revision; } } public sealed record Release1StoryState { public string PlayerId { get; init; } public int Standing { get; init; } public Release1RelationshipState RelationshipState { get; init; } public bool Release1Recognized { get; init; } public IReadOnlyList IntroLogicalCorrelationIds { get { return _introLogicalCorrelationIds; } init { _introLogicalCorrelationIds = Freeze(value, "IntroLogicalCorrelationIds"); } } public IReadOnlyList RecognitionLogicalCorrelationIds { get { return _recognitionLogicalCorrelationIds; } init { _recognitionLogicalCorrelationIds = Freeze(value, "RecognitionLogicalCorrelationIds"); } } public IReadOnlyList Missions { get { return _missions; } init { _missions = Freeze(value, "Missions"); } } public IReadOnlyList NativeEffects { get { return _nativeEffects; } init { _nativeEffects = Freeze(value, "NativeEffects"); } } public IReadOnlyList PhonePresentationAttempts { get { return _phonePresentationAttempts; } init { _phonePresentationAttempts = Freeze(value, "PhonePresentationAttempts"); } } public IReadOnlyList SmallCourtesyAssignments { get { return _smallCourtesyAssignments; } init { _smallCourtesyAssignments = Freeze(value, "SmallCourtesyAssignments"); } } public IReadOnlyList PresentationReceipts { get { return _presentationReceipts; } init { _presentationReceipts = Freeze(value, "PresentationReceipts"); } } public IReadOnlyList WrongAddressAssignments { get { return _wrongAddressAssignments; } init { _wrongAddressAssignments = Freeze(value, "WrongAddressAssignments"); } } public IReadOnlyList WrongAddressProgress { get { return _wrongAddressProgress; } init { _wrongAddressProgress = Freeze(value, "WrongAddressProgress"); } } public IReadOnlyList RoomWithNoNameAssignments { get { return _roomWithNoNameAssignments; } init { _roomWithNoNameAssignments = Freeze(value, "RoomWithNoNameAssignments"); } } public IReadOnlyList RoomWithNoNameProgress { get { return _roomWithNoNameProgress; } init { _roomWithNoNameProgress = Freeze(value, "RoomWithNoNameProgress"); } } public IReadOnlyList ShortNoticeAssignments { get { return _shortNoticeAssignments; } init { _shortNoticeAssignments = Freeze(value, "ShortNoticeAssignments"); } } public IReadOnlyList ShortNoticeProgress { get { return _shortNoticeProgress; } init { _shortNoticeProgress = Freeze(value, "ShortNoticeProgress"); } } public IReadOnlyList KeepTheLightsOffAssignments { get { return _keepTheLightsOffAssignments; } init { _keepTheLightsOffAssignments = Freeze(value, "KeepTheLightsOffAssignments"); } } public IReadOnlyList KeepTheLightsOffProgress { get { return _keepTheLightsOffProgress; } init { _keepTheLightsOffProgress = Freeze(value, "KeepTheLightsOffProgress"); } } public IReadOnlyList TheEnvelopeAssignments { get { return _theEnvelopeAssignments; } init { _theEnvelopeAssignments = Freeze(value, "TheEnvelopeAssignments"); } } public IReadOnlyList TheEnvelopeProgress { get { return _theEnvelopeProgress; } init { _theEnvelopeProgress = Freeze(value, "TheEnvelopeProgress"); } } public Release1ChiefRecord? ChiefRecord { get; init; } public long Revision { get; init; } public Release1StandingBand StandingBand { get { int standing = Standing; if (standing < 60) { if (standing >= 20) { if (standing < 40) { return Release1StandingBand.Tolerated; } return Release1StandingBand.Useful; } return Release1StandingBand.Cold; } if (standing < 80) { return Release1StandingBand.Trusted; } return Release1StandingBand.RegionalProspect; } } private IReadOnlyList _introLogicalCorrelationIds = ImmutableArray.Empty; private IReadOnlyList _recognitionLogicalCorrelationIds = ImmutableArray.Empty; private IReadOnlyList _missions = ImmutableArray.Empty; private IReadOnlyList _nativeEffects = ImmutableArray.Empty; private IReadOnlyList _phonePresentationAttempts = ImmutableArray.Empty; private IReadOnlyList _smallCourtesyAssignments = ImmutableArray.Empty; private IReadOnlyList _presentationReceipts = ImmutableArray.Empty; private IReadOnlyList _wrongAddressAssignments = ImmutableArray.Empty; private IReadOnlyList _wrongAddressProgress = ImmutableArray.Empty; private IReadOnlyList _roomWithNoNameAssignments = ImmutableArray.Empty; private IReadOnlyList _roomWithNoNameProgress = ImmutableArray.Empty; private IReadOnlyList _shortNoticeAssignments = ImmutableArray.Empty; private IReadOnlyList _shortNoticeProgress = ImmutableArray.Empty; private IReadOnlyList _keepTheLightsOffAssignments = ImmutableArray.Empty; private IReadOnlyList _keepTheLightsOffProgress = ImmutableArray.Empty; private IReadOnlyList _theEnvelopeAssignments = ImmutableArray.Empty; private IReadOnlyList _theEnvelopeProgress = ImmutableArray.Empty; public Release1StoryState(string PlayerId, int Standing, Release1RelationshipState RelationshipState, bool Release1Recognized, IReadOnlyList IntroLogicalCorrelationIds, IReadOnlyList RecognitionLogicalCorrelationIds, IReadOnlyList Missions, IReadOnlyList NativeEffects, long Revision, Release1ChiefRecord? ChiefRecord = null) { this.PlayerId = PlayerId; this.Standing = Standing; this.RelationshipState = RelationshipState; this.Release1Recognized = Release1Recognized; this.IntroLogicalCorrelationIds = CopyIds(IntroLogicalCorrelationIds, "IntroLogicalCorrelationIds"); this.RecognitionLogicalCorrelationIds = CopyIds(RecognitionLogicalCorrelationIds, "RecognitionLogicalCorrelationIds"); this.Missions = Missions?.ToArray() ?? throw new ArgumentNullException("Missions"); this.NativeEffects = NativeEffects?.ToArray() ?? throw new ArgumentNullException("NativeEffects"); this.Revision = Revision; this.ChiefRecord = ChiefRecord; Validate(); } public static Release1StoryState CreateAccepted(string playerId, string introCorrelationId) { if (!Release1LogicalCorrelation.TryParse(introCorrelationId, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.intro" || correlation.Attempt != 0 || correlation.TransitionKind != Release1TransitionKind.IntroAccepted || correlation.PlayerId != playerId) { throw new ArgumentException("Intro correlation is not canonical.", "introCorrelationId"); } Release1MissionRecord[] missions = Release1MissionCatalog.All.Select((Release1MissionDefinition definition, int index) => NewMission(definition.MissionKey, (index == 0) ? Release1MissionState.Offered : Release1MissionState.Locked)).ToArray(); return new Release1StoryState(playerId, 20, Release1RelationshipState.Accepted, Release1Recognized: false, new string[1] { introCorrelationId }, Array.Empty(), missions, Array.Empty(), 1L); } public void Validate() { ValidatePlayerId(PlayerId); int standing = Standing; if ((standing < 0 || standing > 100) ? true : false) { throw new ArgumentOutOfRangeException("Standing"); } if (Revision < 0) { throw new ArgumentOutOfRangeException("Revision"); } if (Missions.Count != Release1MissionCatalog.All.Count) { throw new ArgumentException("Story state must contain exactly six missions.", "Missions"); } for (int i = 0; i < Missions.Count; i++) { Release1MissionRecord release1MissionRecord = Missions[i] ?? throw new ArgumentException("Mission records cannot be null.", "Missions"); release1MissionRecord.Validate(); if (release1MissionRecord.MissionKey != Release1MissionCatalog.All[i].MissionKey) { throw new ArgumentException("Mission records must use canonical catalog order.", "Missions"); } foreach (string acceptedLogicalCorrelation in release1MissionRecord.AcceptedLogicalCorrelations) { Release1LogicalCorrelation correlation; bool flag = !Release1LogicalCorrelation.TryParse(acceptedLogicalCorrelation, out correlation) || correlation.PlayerId != PlayerId || correlation.MissionKey != release1MissionRecord.MissionKey || correlation.Attempt < 1 || correlation.Attempt > release1MissionRecord.Attempt; if (!flag) { Release1TransitionKind transitionKind = correlation.TransitionKind; bool flag2 = (((uint)transitionKind <= 1u || transitionKind == Release1TransitionKind.Release1Recognized) ? true : false); flag = flag2; } if (flag) { throw new ArgumentException("Mission correlation did not match its containing player, mission, attempt, or transition.", "Missions"); } } foreach (string presentationCorrelationId in release1MissionRecord.PresentationCorrelationIds) { if (!Release1LogicalCorrelation.TryParse(presentationCorrelationId, out Release1LogicalCorrelation correlation2) || correlation2.PlayerId != PlayerId || correlation2.MissionKey != release1MissionRecord.MissionKey || correlation2.Attempt < 1 || correlation2.Attempt > release1MissionRecord.Attempt) { throw new ArgumentException("Presentation correlation did not match its containing player, mission, or attempt.", "Missions"); } } } if (Missions.Select((Release1MissionRecord m) => m.MissionKey).Distinct(StringComparer.Ordinal).Count() != Missions.Count) { throw new ArgumentException("Mission keys must be unique.", "Missions"); } if (Release1Recognized && RecognitionLogicalCorrelationIds.Count == 0) { throw new ArgumentException("Recognized stories require a recognition correlation.", "Release1Recognized"); } if (!Release1Recognized && RecognitionLogicalCorrelationIds.Count != 0) { throw new ArgumentException("Unrecognized stories cannot contain recognition correlations.", "RecognitionLogicalCorrelationIds"); } if (RelationshipState == Release1RelationshipState.Accepted && IntroLogicalCorrelationIds.Count == 0) { throw new ArgumentException("Accepted stories require an intro correlation.", "RelationshipState"); } if (IntroLogicalCorrelationIds.Concat(RecognitionLogicalCorrelationIds).Distinct(StringComparer.Ordinal).Count() != IntroLogicalCorrelationIds.Count + RecognitionLogicalCorrelationIds.Count) { throw new ArgumentException("Story correlations must be unique."); } foreach (string introLogicalCorrelationId in IntroLogicalCorrelationIds) { Release1LogicalCorrelation correlation3; bool flag = !Release1LogicalCorrelation.TryParse(introLogicalCorrelationId, out correlation3) || correlation3.PlayerId != PlayerId || correlation3.MissionKey != "release1.intro"; if (!flag) { Release1TransitionKind transitionKind = correlation3.TransitionKind; bool flag2 = (uint)transitionKind <= 1u; flag = !flag2; } if (flag) { throw new ArgumentException("Intro correlations must be canonical intro correlations.", "IntroLogicalCorrelationIds"); } } foreach (string recognitionLogicalCorrelationId in RecognitionLogicalCorrelationIds) { if (!Release1LogicalCorrelation.TryParse(recognitionLogicalCorrelationId, out Release1LogicalCorrelation correlation4) || correlation4.PlayerId != PlayerId || correlation4.MissionKey != "release1.intro" || correlation4.TransitionKind != Release1TransitionKind.Release1Recognized) { throw new ArgumentException("Recognition correlations must be canonical recognition correlations.", "RecognitionLogicalCorrelationIds"); } } if (!Enum.IsDefined(RelationshipState)) { throw new ArgumentException("Relationship state is not defined.", "RelationshipState"); } string[] array = NativeEffects.Select((Release1NativeEffectJournalEntry release1NativeEffectJournalEntry3) => release1NativeEffectJournalEntry3?.EffectId ?? throw new ArgumentException("Native effect records cannot be null.", "NativeEffects")).ToArray(); Release1LogicalCorrelation correlation6 = default(Release1LogicalCorrelation); foreach (Release1NativeEffectJournalEntry effect in NativeEffects) { effect.Validate(); bool flag; if (string.Equals(effect.MissionKey, "release1.chief-campbell", StringComparison.Ordinal)) { Release1NativeEffectPhase phase = effect.Phase; flag = (uint)(phase - 1) <= 1u; bool flag3 = flag || effect.ExecutionBlocked; if ((object)ChiefRecord == null || effect.AuthorizedStoryCorrelationId == null || effect.AuthorizedMissionRevision < 0 || !Release1LogicalCorrelation.TryParse(effect.AuthorizedStoryCorrelationId, out Release1LogicalCorrelation correlation5) || correlation5.PlayerId != PlayerId || correlation5.MissionKey != effect.MissionKey || correlation5.Attempt != effect.Attempt || correlation5.TransitionKind != Release1TransitionKind.ChiefPaymentAccepted || !string.Equals(effect.EffectKind, "CashTransfer", StringComparison.Ordinal) || effect.PreparedStoryRevision > Revision || effect.Attempt > (flag3 ? 3 : ChiefRecord.DemandRound) || effect.AuthorizedMissionRevision > ChiefRecord.Revision || (!flag3 && !ChiefRecord.NativeEffectIds.Contains(effect.EffectId, StringComparer.Ordinal)) || (!flag3 && !ChiefRecord.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal))) { throw new ArgumentException("Chief Campbell native effect was not linked to his record.", "NativeEffects"); } if (effect.CommittedStoryCorrelationId != null && effect.CommittedStoryCorrelationId != effect.AuthorizedStoryCorrelationId) { throw new ArgumentException("Committed effects must retain their authorization correlation.", "NativeEffects"); } continue; } flag = effect.AuthorizedStoryCorrelationId == null || effect.AuthorizedMissionRevision < 0 || !Release1LogicalCorrelation.TryParse(effect.AuthorizedStoryCorrelationId, out correlation6) || correlation6.PlayerId != PlayerId || correlation6.MissionKey != effect.MissionKey || correlation6.Attempt != effect.Attempt; if (!flag) { Release1TransitionKind transitionKind = correlation6.TransitionKind; bool flag2 = (((uint)(transitionKind - 5) <= 2u || transitionKind == Release1TransitionKind.MakeGoodAccepted || transitionKind == Release1TransitionKind.RecoveryAccepted) ? true : false); flag = !flag2; } if (flag || effect.PreparedStoryRevision > Revision) { throw new ArgumentException("Native effect authorization did not match this story.", "NativeEffects"); } Release1MissionRecord release1MissionRecord2 = Missions.SingleOrDefault((Release1MissionRecord m) => m.MissionKey == effect.MissionKey && m.Attempt >= effect.Attempt); if ((object)release1MissionRecord2 == null || effect.AuthorizedMissionRevision > release1MissionRecord2.Revision || !release1MissionRecord2.NativeEffectIds.Contains(effect.EffectId, StringComparer.Ordinal) || !release1MissionRecord2.AcceptedLogicalCorrelations.Contains(effect.AuthorizedStoryCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("Native effect was not linked to its containing mission.", "NativeEffects"); } if (correlation6.TransitionKind == Release1TransitionKind.MissionCompleted && release1MissionRecord2.State != Release1MissionState.Satisfied) { throw new ArgumentException("Completion effects require a satisfied mission.", "NativeEffects"); } if (string.Equals(effect.EffectKind, "Reward", StringComparison.Ordinal) && correlation6.TransitionKind != Release1TransitionKind.MissionCompleted) { throw new ArgumentException("Reward effects require a completion authorization.", "NativeEffects"); } if (effect.CommittedStoryCorrelationId != null && effect.CommittedStoryCorrelationId != effect.AuthorizedStoryCorrelationId) { throw new ArgumentException("Committed effects must retain their authorization correlation.", "NativeEffects"); } } if (array.Distinct(StringComparer.Ordinal).Count() != array.Length) { throw new ArgumentException("Native effect IDs must be unique.", "NativeEffects"); } if ((object)ChiefRecord != null) { ChiefRecord.Validate(); foreach (string effectId in ChiefRecord.NativeEffectIds) { Release1NativeEffectJournalEntry release1NativeEffectJournalEntry = NativeEffects.SingleOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effectId); if ((object)release1NativeEffectJournalEntry == null || !string.Equals(release1NativeEffectJournalEntry.MissionKey, "release1.chief-campbell", StringComparison.Ordinal) || release1NativeEffectJournalEntry.Attempt > ChiefRecord.DemandRound) { throw new ArgumentException("Chief Campbell record effect references were not durable and contextual.", "ChiefRecord"); } } } string[] array2 = Missions.SelectMany((Release1MissionRecord mission) => mission.PenaltyReceiptIds).ToArray(); if (array2.Distinct(StringComparer.Ordinal).Count() != array2.Length) { throw new ArgumentException("Penalty receipts must be globally unique.", "Missions"); } foreach (Release1MissionRecord mission in Missions) { foreach (string effectId2 in mission.NativeEffectIds) { Release1NativeEffectJournalEntry release1NativeEffectJournalEntry2 = NativeEffects.SingleOrDefault((Release1NativeEffectJournalEntry candidate) => candidate.EffectId == effectId2); if ((object)release1NativeEffectJournalEntry2 == null || release1NativeEffectJournalEntry2.MissionKey != mission.MissionKey || release1NativeEffectJournalEntry2.Attempt > mission.Attempt) { throw new ArgumentException("Mission native-effect references were not durable and contextual.", "Missions"); } } } foreach (Release1PhonePresentationAttempt presentation in PhonePresentationAttempts) { presentation.Validate(); if (!Release1LogicalCorrelation.TryParse(presentation.CorrelationId, out Release1LogicalCorrelation correlation7) || correlation7.PlayerId != PlayerId || correlation7.MissionKey != presentation.MissionKey || correlation7.Attempt != presentation.Attempt || !Missions.Any((Release1MissionRecord mission) => mission.MissionKey == presentation.MissionKey && mission.Attempt >= presentation.Attempt)) { throw new ArgumentException("Phone presentation attempt did not match story identity or mission state.", "PhonePresentationAttempts"); } } if (PhonePresentationAttempts.Select((Release1PhonePresentationAttempt attempt) => attempt.CorrelationId).Distinct(StringComparer.Ordinal).Count() != PhonePresentationAttempts.Count) { throw new ArgumentException("Phone presentation correlations must be unique.", "PhonePresentationAttempts"); } foreach (Release1PresentationReceipt presentationReceipt in PresentationReceipts) { Release1PresentationReceipt obj = presentationReceipt ?? throw new ArgumentException("Presentation receipts cannot be null.", "PresentationReceipts"); obj.Validate(); if (!Release1LogicalCorrelation.TryParse(obj.CorrelationId, out Release1LogicalCorrelation correlation8) || correlation8.PlayerId != PlayerId) { throw new ArgumentException("Presentation receipt did not match story identity.", "PresentationReceipts"); } } if (PresentationReceipts.Select((Release1PresentationReceipt receipt) => receipt.CorrelationId).Distinct(StringComparer.Ordinal).Count() != PresentationReceipts.Count) { throw new ArgumentException("Presentation receipt correlations must be unique.", "PresentationReceipts"); } ValidateSmallCourtesyAssignments(); ValidateWrongAddress(); ValidateRoomWithNoName(); ValidateShortNotice(); ValidateKeepTheLightsOff(); ValidateTheEnvelope(); } public bool ValueEquals(Release1StoryState? other) { if ((object)other != null && PlayerId == other.PlayerId && Standing == other.Standing && RelationshipState == other.RelationshipState && Release1Recognized == other.Release1Recognized && Revision == other.Revision && SequenceEqual(IntroLogicalCorrelationIds, other.IntroLogicalCorrelationIds) && SequenceEqual(RecognitionLogicalCorrelationIds, other.RecognitionLogicalCorrelationIds) && Missions.Count == other.Missions.Count && Missions.Zip(other.Missions).All(((Release1MissionRecord First, Release1MissionRecord Second) pair) => pair.First.ValueEquals(pair.Second)) && NativeEffects.Count == other.NativeEffects.Count && NativeEffects.Zip(other.NativeEffects).All(((Release1NativeEffectJournalEntry First, Release1NativeEffectJournalEntry Second) pair) => pair.First.ValueEquals(pair.Second)) && PhonePresentationAttempts.Count == other.PhonePresentationAttempts.Count && PhonePresentationAttempts.Zip(other.PhonePresentationAttempts).All(((Release1PhonePresentationAttempt First, Release1PhonePresentationAttempt Second) pair) => pair.First == pair.Second) && SmallCourtesyAssignments.Count == other.SmallCourtesyAssignments.Count && SmallCourtesyAssignments.Zip(other.SmallCourtesyAssignments).All(((Release1SmallCourtesyAssignment First, Release1SmallCourtesyAssignment Second) pair) => pair.First == pair.Second) && PresentationReceipts.Count == other.PresentationReceipts.Count && PresentationReceipts.Zip(other.PresentationReceipts).All(((Release1PresentationReceipt First, Release1PresentationReceipt Second) pair) => pair.First == pair.Second) && WrongAddressAssignments.Count == other.WrongAddressAssignments.Count && WrongAddressAssignments.Zip(other.WrongAddressAssignments).All(((Release1WrongAddressAssignment First, Release1WrongAddressAssignment Second) pair) => pair.First == pair.Second) && WrongAddressProgress.Count == other.WrongAddressProgress.Count && WrongAddressProgress.Zip(other.WrongAddressProgress).All(((Release1WrongAddressProgress First, Release1WrongAddressProgress Second) pair) => pair.First == pair.Second) && RoomWithNoNameAssignments.Count == other.RoomWithNoNameAssignments.Count && RoomWithNoNameAssignments.Zip(other.RoomWithNoNameAssignments).All(((Release1RoomWithNoNameAssignment First, Release1RoomWithNoNameAssignment Second) pair) => pair.First == pair.Second) && RoomWithNoNameProgress.Count == other.RoomWithNoNameProgress.Count && RoomWithNoNameProgress.Zip(other.RoomWithNoNameProgress).All(((Release1RoomWithNoNameProgress First, Release1RoomWithNoNameProgress Second) pair) => pair.First == pair.Second) && ShortNoticeAssignments.Count == other.ShortNoticeAssignments.Count && ShortNoticeAssignments.Zip(other.ShortNoticeAssignments).All(((Release1ShortNoticeAssignment First, Release1ShortNoticeAssignment Second) pair) => pair.First == pair.Second) && ShortNoticeProgress.Count == other.ShortNoticeProgress.Count && ShortNoticeProgress.Zip(other.ShortNoticeProgress).All(((Release1ShortNoticeProgress First, Release1ShortNoticeProgress Second) pair) => pair.First == pair.Second) && KeepTheLightsOffAssignments.Count == other.KeepTheLightsOffAssignments.Count && KeepTheLightsOffAssignments.Zip(other.KeepTheLightsOffAssignments).All(((Release1KeepTheLightsOffAssignment First, Release1KeepTheLightsOffAssignment Second) pair) => pair.First == pair.Second) && KeepTheLightsOffProgress.Count == other.KeepTheLightsOffProgress.Count && KeepTheLightsOffProgress.Zip(other.KeepTheLightsOffProgress).All(((Release1KeepTheLightsOffProgress First, Release1KeepTheLightsOffProgress Second) pair) => pair.First == pair.Second) && TheEnvelopeAssignments.Count == other.TheEnvelopeAssignments.Count && TheEnvelopeAssignments.Zip(other.TheEnvelopeAssignments).All(((Release1TheEnvelopeAssignment First, Release1TheEnvelopeAssignment Second) pair) => pair.First == pair.Second) && TheEnvelopeProgress.Count == other.TheEnvelopeProgress.Count && TheEnvelopeProgress.Zip(other.TheEnvelopeProgress).All(((Release1TheEnvelopeProgress First, Release1TheEnvelopeProgress Second) pair) => pair.First == pair.Second) && (object)ChiefRecord == null == ((object)other.ChiefRecord == null)) { if ((object)ChiefRecord != null) { return ChiefRecord.ValueEquals(other.ChiefRecord); } return true; } return false; } public bool Equals(Release1StoryState? other) { return ValueEquals(other); } public override int GetHashCode() { HashCode hashCode = default(HashCode); hashCode.Add(PlayerId); hashCode.Add(Standing); hashCode.Add(RelationshipState); hashCode.Add(Release1Recognized); hashCode.Add(Revision); foreach (string introLogicalCorrelationId in IntroLogicalCorrelationIds) { hashCode.Add(introLogicalCorrelationId); } foreach (string recognitionLogicalCorrelationId in RecognitionLogicalCorrelationIds) { hashCode.Add(recognitionLogicalCorrelationId); } foreach (Release1MissionRecord mission in Missions) { hashCode.Add(mission); } foreach (Release1NativeEffectJournalEntry nativeEffect in NativeEffects) { hashCode.Add(nativeEffect); } foreach (Release1PhonePresentationAttempt phonePresentationAttempt in PhonePresentationAttempts) { hashCode.Add(phonePresentationAttempt); } foreach (Release1SmallCourtesyAssignment smallCourtesyAssignment in SmallCourtesyAssignments) { hashCode.Add(smallCourtesyAssignment); } foreach (Release1PresentationReceipt presentationReceipt in PresentationReceipts) { hashCode.Add(presentationReceipt); } foreach (Release1WrongAddressAssignment wrongAddressAssignment in WrongAddressAssignments) { hashCode.Add(wrongAddressAssignment); } foreach (Release1WrongAddressProgress item in WrongAddressProgress) { hashCode.Add(item); } foreach (Release1RoomWithNoNameAssignment roomWithNoNameAssignment in RoomWithNoNameAssignments) { hashCode.Add(roomWithNoNameAssignment); } foreach (Release1RoomWithNoNameProgress item2 in RoomWithNoNameProgress) { hashCode.Add(item2); } foreach (Release1ShortNoticeAssignment shortNoticeAssignment in ShortNoticeAssignments) { hashCode.Add(shortNoticeAssignment); } foreach (Release1ShortNoticeProgress item3 in ShortNoticeProgress) { hashCode.Add(item3); } foreach (Release1KeepTheLightsOffAssignment keepTheLightsOffAssignment in KeepTheLightsOffAssignments) { hashCode.Add(keepTheLightsOffAssignment); } foreach (Release1KeepTheLightsOffProgress item4 in KeepTheLightsOffProgress) { hashCode.Add(item4); } foreach (Release1TheEnvelopeAssignment theEnvelopeAssignment in TheEnvelopeAssignments) { hashCode.Add(theEnvelopeAssignment); } foreach (Release1TheEnvelopeProgress item5 in TheEnvelopeProgress) { hashCode.Add(item5); } hashCode.Add(ChiefRecord); return hashCode.ToHashCode(); } private void ValidateSmallCourtesyAssignments() { Release1MissionRecord release1MissionRecord = Missions.Single((Release1MissionRecord mission) => mission.MissionKey == "release1.small-courtesy"); foreach (Release1SmallCourtesyAssignment smallCourtesyAssignment in SmallCourtesyAssignments) { if ((object)smallCourtesyAssignment == null) { throw new ArgumentException("Small Courtesy assignments cannot be null.", "SmallCourtesyAssignments"); } smallCourtesyAssignment.Validate(); if (!Release1LogicalCorrelation.TryParse(smallCourtesyAssignment.AuthorizationCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != PlayerId) { throw new ArgumentException("Small Courtesy assignment did not match story identity.", "SmallCourtesyAssignments"); } if (release1MissionRecord.Attempt < smallCourtesyAssignment.Attempt || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(smallCourtesyAssignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("Small Courtesy assignment was not authorized by its containing mission attempt.", "SmallCourtesyAssignments"); } } if (SmallCourtesyAssignments.Select((Release1SmallCourtesyAssignment assignment) => (MissionKey: assignment.MissionKey, Attempt: assignment.Attempt)).Distinct().Count() != SmallCourtesyAssignments.Count) { throw new ArgumentException("Small Courtesy mission-attempt assignments must be unique.", "SmallCourtesyAssignments"); } if (SmallCourtesyAssignments.Count != 0) { Release1SmallCourtesyAssignment first = SmallCourtesyAssignments[0]; if (SmallCourtesyAssignments.Any((Release1SmallCourtesyAssignment assignment) => !string.Equals(assignment.ProductId, first.ProductId, StringComparison.Ordinal) || !string.Equals(assignment.ProductName, first.ProductName, StringComparison.Ordinal))) { throw new ArgumentException("Small Courtesy product identity must remain frozen across attempts.", "SmallCourtesyAssignments"); } Release1SmallCourtesyAssignment laterStage = SmallCourtesyAssignments.FirstOrDefault((Release1SmallCourtesyAssignment assignment) => assignment.Mode != Release1SmallCourtesyAssignmentMode.Primary); if ((object)laterStage != null && SmallCourtesyAssignments.Any((Release1SmallCourtesyAssignment assignment) => assignment.Mode != Release1SmallCourtesyAssignmentMode.Primary && (!string.Equals(assignment.PackagingId, laterStage.PackagingId, StringComparison.Ordinal) || !string.Equals(assignment.PackagingName, laterStage.PackagingName, StringComparison.Ordinal)))) { throw new ArgumentException("Small Courtesy make-good and recovery packaging must remain frozen.", "SmallCourtesyAssignments"); } } } private void ValidateWrongAddress() { Release1MissionRecord release1MissionRecord = Missions.Single((Release1MissionRecord mission) => mission.MissionKey == "release1.wrong-address"); foreach (Release1WrongAddressAssignment wrongAddressAssignment in WrongAddressAssignments) { if ((object)wrongAddressAssignment == null) { throw new ArgumentException("Wrong Address assignments cannot be null.", "WrongAddressAssignments"); } wrongAddressAssignment.Validate(); if (!Release1LogicalCorrelation.TryParse(wrongAddressAssignment.AuthorizationCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != PlayerId) { throw new ArgumentException("Wrong Address assignment did not match story identity.", "WrongAddressAssignments"); } if (release1MissionRecord.Attempt < wrongAddressAssignment.Attempt || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(wrongAddressAssignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("Wrong Address assignment was not authorized by its containing mission attempt.", "WrongAddressAssignments"); } } if (WrongAddressAssignments.Select((Release1WrongAddressAssignment assignment) => (MissionKey: assignment.MissionKey, Attempt: assignment.Attempt)).Distinct().Count() != WrongAddressAssignments.Count) { throw new ArgumentException("Wrong Address mission-attempt assignments must be unique.", "WrongAddressAssignments"); } if (WrongAddressAssignments.Count > 0) { Release1WrongAddressAssignment first = WrongAddressAssignments[0]; if (WrongAddressAssignments.Any((Release1WrongAddressAssignment assignment) => !string.Equals(assignment.ProductId, first.ProductId, StringComparison.Ordinal) || !string.Equals(assignment.ProductName, first.ProductName, StringComparison.Ordinal))) { throw new ArgumentException("Wrong Address product identity must remain frozen across attempts.", "WrongAddressAssignments"); } Release1WrongAddressAssignment laterStage = WrongAddressAssignments.FirstOrDefault((Release1WrongAddressAssignment assignment) => assignment.Mode != Release1WrongAddressAssignmentMode.Primary); if ((object)laterStage != null && WrongAddressAssignments.Any((Release1WrongAddressAssignment assignment) => assignment.Mode != Release1WrongAddressAssignmentMode.Primary && (!string.Equals(assignment.PackagingId, laterStage.PackagingId, StringComparison.Ordinal) || !string.Equals(assignment.PackagingName, laterStage.PackagingName, StringComparison.Ordinal)))) { throw new ArgumentException("Wrong Address make-good and recovery packaging must remain frozen.", "WrongAddressAssignments"); } } foreach (Release1WrongAddressProgress progress in WrongAddressProgress) { if ((object)progress == null) { throw new ArgumentException("Wrong Address progress cannot be null.", "WrongAddressProgress"); } progress.Validate(); if (!WrongAddressAssignments.Any((Release1WrongAddressAssignment assignment) => assignment.Attempt == progress.Attempt)) { throw new ArgumentException("Wrong Address progress had no accepted assignment for its attempt.", "WrongAddressProgress"); } } if (WrongAddressProgress.Select((Release1WrongAddressProgress release1WrongAddressProgress) => (MissionKey: release1WrongAddressProgress.MissionKey, Attempt: release1WrongAddressProgress.Attempt)).Distinct().Count() != WrongAddressProgress.Count) { throw new ArgumentException("Wrong Address mission-attempt progress must be unique.", "WrongAddressProgress"); } } private void ValidateRoomWithNoName() { Release1MissionRecord release1MissionRecord = Missions.Single((Release1MissionRecord mission) => mission.MissionKey == "release1.room-with-no-name"); foreach (Release1RoomWithNoNameAssignment roomWithNoNameAssignment in RoomWithNoNameAssignments) { if ((object)roomWithNoNameAssignment == null) { throw new ArgumentException("Room With No Name assignments cannot be null.", "RoomWithNoNameAssignments"); } roomWithNoNameAssignment.Validate(); if (!Release1LogicalCorrelation.TryParse(roomWithNoNameAssignment.AuthorizationCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != PlayerId) { throw new ArgumentException("Room With No Name assignment did not match story identity.", "RoomWithNoNameAssignments"); } if (release1MissionRecord.Attempt < roomWithNoNameAssignment.Attempt || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(roomWithNoNameAssignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("Room With No Name assignment was not authorized by its containing mission attempt.", "RoomWithNoNameAssignments"); } } if (RoomWithNoNameAssignments.Select((Release1RoomWithNoNameAssignment assignment) => (MissionKey: assignment.MissionKey, Attempt: assignment.Attempt)).Distinct().Count() != RoomWithNoNameAssignments.Count) { throw new ArgumentException("Room With No Name mission-attempt assignments must be unique.", "RoomWithNoNameAssignments"); } if (RoomWithNoNameAssignments.Count > 0) { Release1RoomWithNoNameAssignment first = RoomWithNoNameAssignments[0]; if (RoomWithNoNameAssignments.Any((Release1RoomWithNoNameAssignment assignment) => !string.Equals(assignment.ProductId, first.ProductId, StringComparison.Ordinal) || !string.Equals(assignment.ProductName, first.ProductName, StringComparison.Ordinal))) { throw new ArgumentException("Room With No Name product identity must remain frozen across attempts.", "RoomWithNoNameAssignments"); } Release1RoomWithNoNameAssignment laterStage = RoomWithNoNameAssignments.FirstOrDefault((Release1RoomWithNoNameAssignment assignment) => assignment.Mode != Release1RoomWithNoNameAssignmentMode.Primary); if ((object)laterStage != null && RoomWithNoNameAssignments.Any((Release1RoomWithNoNameAssignment assignment) => assignment.Mode != Release1RoomWithNoNameAssignmentMode.Primary && (!string.Equals(assignment.PackagingId, laterStage.PackagingId, StringComparison.Ordinal) || !string.Equals(assignment.PackagingName, laterStage.PackagingName, StringComparison.Ordinal)))) { throw new ArgumentException("Room With No Name make-good and recovery packaging must remain frozen.", "RoomWithNoNameAssignments"); } } foreach (Release1RoomWithNoNameProgress progress in RoomWithNoNameProgress) { if ((object)progress == null) { throw new ArgumentException("Room With No Name progress cannot be null.", "RoomWithNoNameProgress"); } progress.Validate(); if (!RoomWithNoNameAssignments.Any((Release1RoomWithNoNameAssignment assignment) => assignment.Attempt == progress.Attempt)) { throw new ArgumentException("Room With No Name progress had no accepted assignment for its attempt.", "RoomWithNoNameProgress"); } } if (RoomWithNoNameProgress.Select((Release1RoomWithNoNameProgress release1RoomWithNoNameProgress) => (MissionKey: release1RoomWithNoNameProgress.MissionKey, Attempt: release1RoomWithNoNameProgress.Attempt)).Distinct().Count() != RoomWithNoNameProgress.Count) { throw new ArgumentException("Room With No Name mission-attempt progress must be unique.", "RoomWithNoNameProgress"); } } private void ValidateShortNotice() { Release1MissionRecord release1MissionRecord = Missions.Single((Release1MissionRecord mission) => mission.MissionKey == "release1.short-notice"); foreach (Release1ShortNoticeAssignment shortNoticeAssignment in ShortNoticeAssignments) { if ((object)shortNoticeAssignment == null) { throw new ArgumentException("Short Notice assignments cannot be null.", "ShortNoticeAssignments"); } shortNoticeAssignment.Validate(); if (!Release1LogicalCorrelation.TryParse(shortNoticeAssignment.AuthorizationCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != PlayerId) { throw new ArgumentException("Short Notice assignment did not match story identity.", "ShortNoticeAssignments"); } if (release1MissionRecord.Attempt < shortNoticeAssignment.Attempt || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(shortNoticeAssignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("Short Notice assignment was not authorized by its containing mission attempt.", "ShortNoticeAssignments"); } } if (ShortNoticeAssignments.Select((Release1ShortNoticeAssignment assignment) => (MissionKey: assignment.MissionKey, Attempt: assignment.Attempt)).Distinct().Count() != ShortNoticeAssignments.Count) { throw new ArgumentException("Short Notice mission-attempt assignments must be unique.", "ShortNoticeAssignments"); } if (ShortNoticeAssignments.Count > 0) { Release1ShortNoticeAssignment first = ShortNoticeAssignments[0]; if (ShortNoticeAssignments.Any((Release1ShortNoticeAssignment assignment) => !string.Equals(assignment.ProductId, first.ProductId, StringComparison.Ordinal) || !string.Equals(assignment.ProductName, first.ProductName, StringComparison.Ordinal) || !string.Equals(assignment.PackagingId, first.PackagingId, StringComparison.Ordinal) || !string.Equals(assignment.PackagingName, first.PackagingName, StringComparison.Ordinal) || assignment.ValueConvention != first.ValueConvention)) { throw new ArgumentException("Short Notice product, packaging, and value convention must remain frozen across attempts.", "ShortNoticeAssignments"); } } foreach (Release1ShortNoticeProgress progress in ShortNoticeProgress) { if ((object)progress == null) { throw new ArgumentException("Short Notice progress cannot be null.", "ShortNoticeProgress"); } progress.Validate(); if (!ShortNoticeAssignments.Any((Release1ShortNoticeAssignment assignment) => assignment.Attempt == progress.Attempt)) { throw new ArgumentException("Short Notice progress had no accepted assignment for its attempt.", "ShortNoticeProgress"); } } if (ShortNoticeProgress.Select((Release1ShortNoticeProgress release1ShortNoticeProgress) => (MissionKey: release1ShortNoticeProgress.MissionKey, Attempt: release1ShortNoticeProgress.Attempt)).Distinct().Count() != ShortNoticeProgress.Count) { throw new ArgumentException("Short Notice mission-attempt progress must be unique.", "ShortNoticeProgress"); } } private void ValidateKeepTheLightsOff() { Release1MissionRecord release1MissionRecord = Missions.Single((Release1MissionRecord mission) => mission.MissionKey == "release1.keep-the-lights-off"); foreach (Release1KeepTheLightsOffAssignment keepTheLightsOffAssignment in KeepTheLightsOffAssignments) { if ((object)keepTheLightsOffAssignment == null) { throw new ArgumentException("Keep the Lights Off assignments cannot be null.", "KeepTheLightsOffAssignments"); } keepTheLightsOffAssignment.Validate(); if (!Release1LogicalCorrelation.TryParse(keepTheLightsOffAssignment.AuthorizationCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != PlayerId) { throw new ArgumentException("Keep the Lights Off assignment did not match story identity.", "KeepTheLightsOffAssignments"); } if (release1MissionRecord.Attempt < keepTheLightsOffAssignment.Attempt || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(keepTheLightsOffAssignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("Keep the Lights Off assignment was not authorized by its containing mission attempt.", "KeepTheLightsOffAssignments"); } } if (KeepTheLightsOffAssignments.Select((Release1KeepTheLightsOffAssignment assignment) => (MissionKey: assignment.MissionKey, Attempt: assignment.Attempt)).Distinct().Count() != KeepTheLightsOffAssignments.Count) { throw new ArgumentException("Keep the Lights Off mission-attempt assignments must be unique.", "KeepTheLightsOffAssignments"); } foreach (Release1KeepTheLightsOffProgress progress in KeepTheLightsOffProgress) { if ((object)progress == null) { throw new ArgumentException("Keep the Lights Off progress cannot be null.", "KeepTheLightsOffProgress"); } progress.Validate(); if (!KeepTheLightsOffAssignments.Any((Release1KeepTheLightsOffAssignment assignment) => assignment.Attempt == progress.Attempt)) { throw new ArgumentException("Keep the Lights Off progress had no accepted assignment for its attempt.", "KeepTheLightsOffProgress"); } } if (KeepTheLightsOffProgress.Select((Release1KeepTheLightsOffProgress release1KeepTheLightsOffProgress) => (MissionKey: release1KeepTheLightsOffProgress.MissionKey, Attempt: release1KeepTheLightsOffProgress.Attempt)).Distinct().Count() != KeepTheLightsOffProgress.Count) { throw new ArgumentException("Keep the Lights Off mission-attempt progress must be unique.", "KeepTheLightsOffProgress"); } } private void ValidateTheEnvelope() { Release1MissionRecord release1MissionRecord = Missions.Single((Release1MissionRecord mission) => mission.MissionKey == "release1.the-envelope"); foreach (Release1TheEnvelopeAssignment theEnvelopeAssignment in TheEnvelopeAssignments) { if ((object)theEnvelopeAssignment == null) { throw new ArgumentException("The Envelope assignments cannot be null.", "TheEnvelopeAssignments"); } theEnvelopeAssignment.Validate(); if (!Release1LogicalCorrelation.TryParse(theEnvelopeAssignment.AuthorizationCorrelationId, out Release1LogicalCorrelation correlation) || correlation.PlayerId != PlayerId) { throw new ArgumentException("The Envelope assignment did not match story identity.", "TheEnvelopeAssignments"); } if (release1MissionRecord.Attempt < theEnvelopeAssignment.Attempt || !release1MissionRecord.AcceptedLogicalCorrelations.Contains(theEnvelopeAssignment.AuthorizationCorrelationId, StringComparer.Ordinal)) { throw new ArgumentException("The Envelope assignment was not authorized by its containing mission attempt.", "TheEnvelopeAssignments"); } } if (TheEnvelopeAssignments.Select((Release1TheEnvelopeAssignment assignment) => (MissionKey: assignment.MissionKey, Attempt: assignment.Attempt)).Distinct().Count() != TheEnvelopeAssignments.Count) { throw new ArgumentException("The Envelope mission-attempt assignments must be unique.", "TheEnvelopeAssignments"); } foreach (Release1TheEnvelopeProgress progress in TheEnvelopeProgress) { if ((object)progress == null) { throw new ArgumentException("The Envelope progress cannot be null.", "TheEnvelopeProgress"); } progress.Validate(); if (!TheEnvelopeAssignments.Any((Release1TheEnvelopeAssignment assignment) => assignment.Attempt == progress.Attempt)) { throw new ArgumentException("The Envelope progress had no accepted assignment for its attempt.", "TheEnvelopeProgress"); } } if (TheEnvelopeProgress.Select((Release1TheEnvelopeProgress release1TheEnvelopeProgress) => (MissionKey: release1TheEnvelopeProgress.MissionKey, Attempt: release1TheEnvelopeProgress.Attempt)).Distinct().Count() != TheEnvelopeProgress.Count) { throw new ArgumentException("The Envelope mission-attempt progress must be unique.", "TheEnvelopeProgress"); } } private static Release1MissionRecord NewMission(string key, Release1MissionState state) { return new Release1MissionRecord(key, state, 1, null, null, null, Release1MissionOutcome.None, 0, Array.Empty(), 0, Release1RecoveryMode.None, null, QuietConditionAwarded: false, Array.Empty(), Array.Empty(), Array.Empty(), null, 0L); } private static IReadOnlyList CopyIds(IReadOnlyList? values, string name) { if (values == null) { throw new ArgumentNullException(name); } string[] array = values.ToArray(); string[] array2 = array; for (int i = 0; i < array2.Length; i++) { Release1MissionRecord.ValidateCorrelation(array2[i], name); } if (array.Distinct(StringComparer.Ordinal).Count() != array.Length) { throw new ArgumentException("IDs must be unique.", name); } return array; } private static IReadOnlyList Freeze(IReadOnlyList? values, string parameterName) { if (values == null) { throw new ArgumentNullException(parameterName); } return ImmutableArray.CreateRange(values); } private static bool SequenceEqual(IReadOnlyList left, IReadOnlyList right) { return left.SequenceEqual(right, StringComparer.Ordinal); } internal static void ValidatePlayerId(string? playerId) { if (string.IsNullOrWhiteSpace(playerId) || playerId.Length > 128 || playerId.Any(char.IsControl) || playerId.Any(char.IsWhiteSpace) || playerId.Contains('/')) { throw new ArgumentException("Player identity is not canonical.", "playerId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("PlayerId = "); builder.Append((object?)PlayerId); builder.Append(", Standing = "); builder.Append(Standing.ToString()); builder.Append(", RelationshipState = "); builder.Append(RelationshipState.ToString()); builder.Append(", Release1Recognized = "); builder.Append(Release1Recognized.ToString()); builder.Append(", IntroLogicalCorrelationIds = "); builder.Append(IntroLogicalCorrelationIds); builder.Append(", RecognitionLogicalCorrelationIds = "); builder.Append(RecognitionLogicalCorrelationIds); builder.Append(", Missions = "); builder.Append(Missions); builder.Append(", NativeEffects = "); builder.Append(NativeEffects); builder.Append(", PhonePresentationAttempts = "); builder.Append(PhonePresentationAttempts); builder.Append(", SmallCourtesyAssignments = "); builder.Append(SmallCourtesyAssignments); builder.Append(", PresentationReceipts = "); builder.Append(PresentationReceipts); builder.Append(", WrongAddressAssignments = "); builder.Append(WrongAddressAssignments); builder.Append(", WrongAddressProgress = "); builder.Append(WrongAddressProgress); builder.Append(", RoomWithNoNameAssignments = "); builder.Append(RoomWithNoNameAssignments); builder.Append(", RoomWithNoNameProgress = "); builder.Append(RoomWithNoNameProgress); builder.Append(", ShortNoticeAssignments = "); builder.Append(ShortNoticeAssignments); builder.Append(", ShortNoticeProgress = "); builder.Append(ShortNoticeProgress); builder.Append(", KeepTheLightsOffAssignments = "); builder.Append(KeepTheLightsOffAssignments); builder.Append(", KeepTheLightsOffProgress = "); builder.Append(KeepTheLightsOffProgress); builder.Append(", TheEnvelopeAssignments = "); builder.Append(TheEnvelopeAssignments); builder.Append(", TheEnvelopeProgress = "); builder.Append(TheEnvelopeProgress); builder.Append(", ChiefRecord = "); builder.Append(ChiefRecord); builder.Append(", Revision = "); builder.Append(Revision.ToString()); builder.Append(", StandingBand = "); builder.Append(StandingBand.ToString()); return true; } [CompilerGenerated] private Release1StoryState(Release1StoryState original) { PlayerId = original.PlayerId; Standing = original.Standing; RelationshipState = original.RelationshipState; Release1Recognized = original.Release1Recognized; _introLogicalCorrelationIds = original._introLogicalCorrelationIds; _recognitionLogicalCorrelationIds = original._recognitionLogicalCorrelationIds; _missions = original._missions; _nativeEffects = original._nativeEffects; _phonePresentationAttempts = original._phonePresentationAttempts; _smallCourtesyAssignments = original._smallCourtesyAssignments; _presentationReceipts = original._presentationReceipts; _wrongAddressAssignments = original._wrongAddressAssignments; _wrongAddressProgress = original._wrongAddressProgress; _roomWithNoNameAssignments = original._roomWithNoNameAssignments; _roomWithNoNameProgress = original._roomWithNoNameProgress; _shortNoticeAssignments = original._shortNoticeAssignments; _shortNoticeProgress = original._shortNoticeProgress; _keepTheLightsOffAssignments = original._keepTheLightsOffAssignments; _keepTheLightsOffProgress = original._keepTheLightsOffProgress; _theEnvelopeAssignments = original._theEnvelopeAssignments; _theEnvelopeProgress = original._theEnvelopeProgress; ChiefRecord = original.ChiefRecord; Revision = original.Revision; } } public enum Release1TransitionKind { IntroAccepted, IntroDeferred, MissionOffered, MissionDeferred, MissionReoffered, MissionAccepted, MissionActivated, MissionCompleted, MissionAbandoned, RequiredFailure, MakeGoodOffered, MakeGoodDeferred, MakeGoodAccepted, MakeGoodFailed, RecoveryAccepted, RecoveryFailed, Release1Recognized, ChiefPaymentAccepted } public sealed record Release1StoryCommand(Guid SessionEpoch, long LoadEpoch, string PlayerId, string MissionKey, int Attempt, Release1TransitionKind TransitionKind, string ReceiptId, string CorrelationId, string? TermsVersion = null, double? AcceptedGameTimeHours = null, double? DeadlineGameTimeHours = null, Release1CompletionTiming? CompletionTiming = null, string? RewardAuthorizationReceiptId = null, string? QuietConditionReceiptId = null, string? RecognitionReceiptId = null, Release1NativeEffectJournalEntry? PreparedNativeEffect = null); public enum Release1StoryTransitionRejectReason { None, MissingState, InvalidCommand, InvalidCorrelation, CorrelationMismatch, UnknownMission, WrongState, WrongAttempt, InvalidReceipt, DuplicateReceipt, InvalidTerms, InvalidTime, MissingCompletionTiming, MissingRewardAuthorization, MissingQuietReceipt, MissingRecognitionReceipt, TerminalState } public sealed record Release1StoryTransitionResult(bool Accepted, bool Idempotent, Release1StoryState? State, Release1StoryTransitionRejectReason RejectReason, string Message) { public bool Changed { get { if (Accepted) { return !Idempotent; } return false; } } public static Release1StoryTransitionResult Reject(Release1StoryTransitionRejectReason reason, string message, Release1StoryState? state = null) { return new Release1StoryTransitionResult(Accepted: false, Idempotent: false, state, reason, message); } public static Release1StoryTransitionResult AcceptedState(Release1StoryState state, bool idempotent = false, string? message = null) { return new Release1StoryTransitionResult(Accepted: true, idempotent, state, Release1StoryTransitionRejectReason.None, message ?? string.Empty); } } public static class Release1StoryTransitions { private sealed class TransitionException : Exception { public Release1StoryTransitionRejectReason Reason { get; } public TransitionException(Release1StoryTransitionRejectReason reason, string message) : base(message) { Reason = reason; } } public static Release1StoryTransitionResult Apply(Release1StoryState? state, Release1StoryCommand command) { if ((object)command == null) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.InvalidCommand, "Command was null.", state); } if (!Release1LogicalCorrelation.TryParse(command.CorrelationId, out Release1LogicalCorrelation correlation)) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.InvalidCorrelation, "Correlation was not canonical.", state); } if (correlation.PlayerId != command.PlayerId || correlation.MissionKey != command.MissionKey || correlation.Attempt != command.Attempt || correlation.TransitionKind != command.TransitionKind || correlation.ReceiptId != command.ReceiptId) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.CorrelationMismatch, "Correlation did not match the command.", state); } try { Release1StoryState.ValidatePlayerId(command.PlayerId); Release1MissionRecord.ValidateId(command.ReceiptId, "ReceiptId"); } catch (ArgumentException ex) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.InvalidReceipt, ex.Message, state); } if ((object)state == null) { if (command.TransitionKind == Release1TransitionKind.IntroAccepted && command.MissionKey == "release1.intro" && command.Attempt == 0) { return Release1StoryTransitionResult.AcceptedState(Release1StoryState.CreateAccepted(command.PlayerId, command.CorrelationId)); } if (command.TransitionKind == Release1TransitionKind.IntroDeferred && command.MissionKey == "release1.intro" && command.Attempt == 0) { return Release1StoryTransitionResult.AcceptedState(CreateDeferred(command.PlayerId, command.CorrelationId)); } return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.MissingState, "Intro acceptance is required before mission transitions."); } if (state.PlayerId != command.PlayerId) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.CorrelationMismatch, "Player identity did not match state.", state); } if (state.IntroLogicalCorrelationIds.Contains(command.CorrelationId, StringComparer.Ordinal) || state.RecognitionLogicalCorrelationIds.Contains(command.CorrelationId, StringComparer.Ordinal)) { return Release1StoryTransitionResult.AcceptedState(state, idempotent: true, "Duplicate logical correlation was already accepted."); } if (command.MissionKey == "release1.intro") { if (command.TransitionKind == Release1TransitionKind.IntroAccepted && state.RelationshipState == Release1RelationshipState.Deferred) { Release1MissionRecord[] missions = state.Missions.Select((Release1MissionRecord mission, int i) => (i != 0 || mission.State != Release1MissionState.Locked) ? mission : mission with { State = Release1MissionState.Offered, Revision = mission.Revision + 1 }).ToArray(); return Release1StoryTransitionResult.AcceptedState(state with { RelationshipState = Release1RelationshipState.Accepted, IntroLogicalCorrelationIds = state.IntroLogicalCorrelationIds.Append(command.CorrelationId).ToArray(), Missions = missions, Revision = state.Revision + 1 }); } return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.TerminalState, "Intro can only be accepted once.", state); } if (!Release1MissionCatalog.IsMissionKey(command.MissionKey)) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.UnknownMission, "Mission key is not canonical.", state); } if (command.Attempt < 1) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.WrongAttempt, "Mission attempt must be positive.", state); } int num = Release1MissionCatalog.IndexOf(command.MissionKey); Release1MissionRecord release1MissionRecord = state.Missions[num]; if (release1MissionRecord.AcceptedLogicalCorrelations.Contains(command.CorrelationId, StringComparer.Ordinal)) { return Release1StoryTransitionResult.AcceptedState(state, idempotent: true, "Duplicate logical correlation was already accepted."); } Release1TransitionKind transitionKind = command.TransitionKind; bool flag = ((transitionKind == Release1TransitionKind.MakeGoodAccepted || transitionKind == Release1TransitionKind.RecoveryAccepted) ? true : false); bool flag2 = flag; if (release1MissionRecord.Attempt != command.Attempt && (!flag2 || release1MissionRecord.Attempt + 1 != command.Attempt)) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.WrongAttempt, "Mission attempt did not match state.", state); } flag = num > 0 && state.Missions[num - 1].State != Release1MissionState.Satisfied; if (flag) { transitionKind = command.TransitionKind; bool flag3 = ((transitionKind == Release1TransitionKind.MissionOffered || (uint)(transitionKind - 4) <= 3u) ? true : false); flag = flag3; } if (flag) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.WrongState, "Prior mission is not satisfied.", state); } try { Release1StoryTransitionResult result; switch (command.TransitionKind) { case Release1TransitionKind.MissionOffered: if (release1MissionRecord.State != Release1MissionState.Locked) { throw WrongState(); } result = ChangeMission(state, num, release1MissionRecord with { State = Release1MissionState.Offered }, command.CorrelationId); break; case Release1TransitionKind.MissionDeferred: result = ChangeMission(state, num, Require(release1MissionRecord, Release1MissionState.Offered)with { State = Release1MissionState.Deferred }, command.CorrelationId); break; case Release1TransitionKind.MissionReoffered: result = Reoffer(state, num, release1MissionRecord, command); break; case Release1TransitionKind.MissionAccepted: result = AcceptMission(state, num, release1MissionRecord, command); break; case Release1TransitionKind.MissionActivated: result = ChangeMission(state, num, Require(release1MissionRecord, Release1MissionState.Accepted)with { State = Release1MissionState.Active }, command.CorrelationId); break; case Release1TransitionKind.MissionAbandoned: result = FailMission(state, num, release1MissionRecord, command, Release1MissionOutcome.Abandoned, 10, Release1MissionState.Abandoned, Release1MissionState.Accepted, Release1MissionState.Active, Release1MissionState.MakeGoodActive); break; case Release1TransitionKind.RequiredFailure: result = FailMission(state, num, release1MissionRecord, command, Release1MissionOutcome.RequiredFailure, 12, Release1MissionState.MakeGoodOffered, Release1MissionState.Active, Release1MissionState.MakeGoodActive); break; case Release1TransitionKind.MakeGoodOffered: { Release1StoryTransitionResult release1StoryTransitionResult; if (release1MissionRecord.State != Release1MissionState.MakeGoodOffered) { if (release1MissionRecord.State != Release1MissionState.Abandoned) { throw WrongState(); } release1StoryTransitionResult = ChangeMission(state, num, release1MissionRecord with { State = Release1MissionState.MakeGoodOffered, RecoveryMode = Release1RecoveryMode.StandardMakeGood }, command.CorrelationId); } else { release1StoryTransitionResult = Release1StoryTransitionResult.AcceptedState(state, idempotent: true, "Make-good was already offered."); } result = release1StoryTransitionResult; break; } case Release1TransitionKind.MakeGoodDeferred: if (release1MissionRecord.State != Release1MissionState.MakeGoodOffered) { throw WrongState(); } result = Release1StoryTransitionResult.AcceptedState(state, idempotent: true, "Make-good remains available."); break; case Release1TransitionKind.MakeGoodAccepted: result = AcceptMakeGood(state, num, release1MissionRecord, command); break; case Release1TransitionKind.MakeGoodFailed: result = FailMission(state, num, release1MissionRecord, command, Release1MissionOutcome.MakeGoodFailure, 8, Release1MissionState.RecoveryAvailable, Release1MissionState.MakeGoodActive); break; case Release1TransitionKind.RecoveryAccepted: result = AcceptRecovery(state, num, release1MissionRecord, command); break; case Release1TransitionKind.RecoveryFailed: result = ChangeMission(state, num, Require(release1MissionRecord, Release1MissionState.RecoveryActive)with { State = Release1MissionState.RecoveryAvailable, Attempt = release1MissionRecord.Attempt + 1, LastOutcome = Release1MissionOutcome.RecoveryFailure }, command.CorrelationId); break; case Release1TransitionKind.MissionCompleted: result = CompleteMission(state, num, release1MissionRecord, command); break; default: result = Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.InvalidCommand, "Transition kind is not valid for a mission.", state); break; } return result; } catch (TransitionException ex2) { return Release1StoryTransitionResult.Reject(ex2.Reason, ex2.Message, state); } catch (ArgumentException ex3) { return Release1StoryTransitionResult.Reject(Release1StoryTransitionRejectReason.InvalidCommand, ex3.Message, state); } } private static Release1StoryState CreateDeferred(string playerId, string correlation) { return new Release1StoryState(playerId, 20, Release1RelationshipState.Deferred, Release1Recognized: false, new string[1] { correlation }, Array.Empty(), Release1MissionCatalog.All.Select((Release1MissionDefinition m) => NewMission(m.MissionKey, Release1MissionState.Locked)).ToArray(), Array.Empty(), 1L); } private static Release1MissionRecord NewMission(string key, Release1MissionState state) { return new Release1MissionRecord(key, state, 1, null, null, null, Release1MissionOutcome.None, 0, Array.Empty(), 0, Release1RecoveryMode.None, null, QuietConditionAwarded: false, Array.Empty(), Array.Empty(), Array.Empty(), null, 0L); } private static Release1StoryTransitionResult AcceptMission(Release1StoryState state, int index, Release1MissionRecord mission, Release1StoryCommand command) { if (mission.State != Release1MissionState.Offered) { throw WrongState(); } if (string.IsNullOrWhiteSpace(command.TermsVersion)) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidTerms, "Mission terms must be immutable and nonblank."); } ValidateTimes(command.AcceptedGameTimeHours, command.DeadlineGameTimeHours); return ChangeMission(state, index, mission with { State = Release1MissionState.Accepted, TermsVersion = command.TermsVersion, AcceptedGameTimeHours = command.AcceptedGameTimeHours, DeadlineGameTimeHours = command.DeadlineGameTimeHours }, command.CorrelationId); } private static Release1StoryTransitionResult AcceptMakeGood(Release1StoryState state, int index, Release1MissionRecord mission, Release1StoryCommand command) { ValidateTimes(command.AcceptedGameTimeHours, command.DeadlineGameTimeHours); return ChangeMission(state, index, Require(mission, Release1MissionState.MakeGoodOffered)with { State = Release1MissionState.MakeGoodActive, Attempt = mission.Attempt + 1, RecoveryMode = Release1RecoveryMode.StandardMakeGood, AcceptedGameTimeHours = command.AcceptedGameTimeHours, DeadlineGameTimeHours = command.DeadlineGameTimeHours }, command.CorrelationId); } private static Release1StoryTransitionResult AcceptRecovery(Release1StoryState state, int index, Release1MissionRecord mission, Release1StoryCommand command) { if (command.DeadlineGameTimeHours.HasValue) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidTime, "Guaranteed recovery cannot have a deadline."); } ValidateTimes(command.AcceptedGameTimeHours, null); return ChangeMission(state, index, Require(mission, Release1MissionState.RecoveryAvailable)with { State = Release1MissionState.RecoveryActive, Attempt = mission.Attempt + 1, RecoveryMode = Release1RecoveryMode.GuaranteedRecovery, AcceptedGameTimeHours = command.AcceptedGameTimeHours, DeadlineGameTimeHours = null }, command.CorrelationId); } private static Release1StoryTransitionResult FailMission(Release1StoryState state, int index, Release1MissionRecord mission, Release1StoryCommand command, Release1MissionOutcome outcome, int penalty, Release1MissionState nextState, params Release1MissionState[] allowedStates) { if (!allowedStates.Contains(mission.State)) { throw WrongState(); } if (state.Missions.Any((Release1MissionRecord candidate) => candidate.PenaltyReceiptIds.Contains(command.ReceiptId, StringComparer.Ordinal))) { throw new TransitionException(Release1StoryTransitionRejectReason.DuplicateReceipt, "Penalty receipt was already recorded in story state."); } if (mission.State == Release1MissionState.MakeGoodActive) { outcome = Release1MissionOutcome.MakeGoodFailure; penalty = 8; nextState = Release1MissionState.RecoveryAvailable; } if (outcome == Release1MissionOutcome.MakeGoodFailure && mission.MakeGoodFailures >= 1) { throw new TransitionException(Release1StoryTransitionRejectReason.WrongState, "The standard make-good was already consumed."); } int num = Math.Min(penalty, 20 - mission.StandingPenaltyApplied); Release1MissionRecord mission2 = mission with { State = nextState, LastOutcome = outcome, StandingPenaltyApplied = mission.StandingPenaltyApplied + num, PenaltyReceiptIds = mission.PenaltyReceiptIds.Append(command.ReceiptId).ToArray(), MakeGoodFailures = ((outcome == Release1MissionOutcome.MakeGoodFailure) ? 1 : mission.MakeGoodFailures), RecoveryMode = ((nextState != Release1MissionState.RecoveryAvailable) ? Release1RecoveryMode.StandardMakeGood : Release1RecoveryMode.GuaranteedRecovery), AcceptedGameTimeHours = ((nextState == Release1MissionState.RecoveryAvailable) ? ((double?)null) : mission.AcceptedGameTimeHours), DeadlineGameTimeHours = ((nextState == Release1MissionState.RecoveryAvailable) ? ((double?)null) : mission.DeadlineGameTimeHours), AcceptedLogicalCorrelations = mission.AcceptedLogicalCorrelations.Append(command.CorrelationId).ToArray() }; return ChangeMission(state with { Standing = Math.Max(0, state.Standing - num) }, index, mission2); } private static Release1StoryTransitionResult CompleteMission(Release1StoryState state, int index, Release1MissionRecord mission, Release1StoryCommand command) { Release1MissionState state2 = mission.State; if ((state2 != Release1MissionState.Active && state2 != Release1MissionState.MakeGoodActive && state2 != Release1MissionState.RecoveryActive) || 1 == 0) { throw WrongState(); } if ((object)command.PreparedNativeEffect != null && (state.NativeEffects.Any((Release1NativeEffectJournalEntry effect) => effect.EffectId == command.PreparedNativeEffect.EffectId) || mission.NativeEffectIds.Contains(command.PreparedNativeEffect.EffectId, StringComparer.Ordinal))) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidCommand, "Native effect ID was already recorded."); } if (!command.CompletionTiming.HasValue) { throw new TransitionException(Release1StoryTransitionRejectReason.MissingCompletionTiming, "Completion timing is required."); } if (string.IsNullOrWhiteSpace(command.RewardAuthorizationReceiptId)) { throw new TransitionException(Release1StoryTransitionRejectReason.MissingRewardAuthorization, "Reward authorization is required."); } Release1MissionRecord.ValidateId(command.RewardAuthorizationReceiptId, "RewardAuthorizationReceiptId"); bool flag = command.QuietConditionReceiptId != null; if (flag && (command.QuietConditionReceiptId == command.ReceiptId || command.QuietConditionReceiptId == command.RewardAuthorizationReceiptId)) { throw new TransitionException(Release1StoryTransitionRejectReason.MissingQuietReceipt, "Quiet receipt must be distinct."); } if (command.QuietConditionReceiptId != null) { Release1MissionRecord.ValidateId(command.QuietConditionReceiptId, "QuietConditionReceiptId"); } bool flag2 = index == Release1MissionCatalog.All.Count - 1; string element = null; if (flag2) { if (string.IsNullOrWhiteSpace(command.RecognitionReceiptId) || command.RecognitionReceiptId == command.ReceiptId) { throw new TransitionException(Release1StoryTransitionRejectReason.MissingRecognitionReceipt, "Final mission requires a distinct recognition receipt."); } Release1MissionRecord.ValidateId(command.RecognitionReceiptId, "RecognitionReceiptId"); element = Release1LogicalCorrelation.Create(state.PlayerId, "release1.intro", 0, Release1TransitionKind.Release1Recognized, command.RecognitionReceiptId).Value; } int num = ((command.CompletionTiming == Release1CompletionTiming.OnTime) ? 10 : 5); int num2 = Math.Clamp(state.Standing + mission.StandingPenaltyApplied + num + (flag ? 2 : 0), 0, 100); if (flag2) { num2 = Math.Max(num2, 80); } Release1MissionRecord obj = mission with { State = Release1MissionState.Satisfied, LastOutcome = ((command.CompletionTiming == Release1CompletionTiming.OnTime) ? Release1MissionOutcome.OnTime : Release1MissionOutcome.Late), StandingPenaltyApplied = 0, RewardAuthorizationReceiptId = command.RewardAuthorizationReceiptId, QuietConditionAwarded = (mission.QuietConditionAwarded || flag), AcceptedLogicalCorrelations = mission.AcceptedLogicalCorrelations.Append(command.CorrelationId).ToArray() }; IReadOnlyList nativeEffectIds; if ((object)command.PreparedNativeEffect != null) { IReadOnlyList readOnlyList = mission.NativeEffectIds.Append(command.PreparedNativeEffect.EffectId).ToArray(); nativeEffectIds = readOnlyList; } else { nativeEffectIds = mission.NativeEffectIds; } obj.NativeEffectIds = nativeEffectIds; Release1MissionRecord mission2 = obj; if ((object)command.PreparedNativeEffect != null && (command.PreparedNativeEffect.MissionKey != mission.MissionKey || command.PreparedNativeEffect.Attempt != mission.Attempt || command.PreparedNativeEffect.Phase != Release1NativeEffectPhase.Prepared)) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidCommand, "Prepared native effect did not match the completing mission."); } if ((object)command.PreparedNativeEffect != null && command.PreparedNativeEffect.AuthorizedStoryCorrelationId != null && command.PreparedNativeEffect.AuthorizedStoryCorrelationId != command.CorrelationId) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidCommand, "Prepared native effect authorization did not match the completing transition."); } if ((object)command.PreparedNativeEffect != null && command.PreparedNativeEffect.AuthorizedMissionRevision >= 0 && command.PreparedNativeEffect.AuthorizedMissionRevision != mission.Revision) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidCommand, "Prepared native effect mission revision did not match the completing transition."); } Release1StoryState obj2 = state with { Standing = num2, Release1Recognized = (state.Release1Recognized || flag2) }; IReadOnlyList recognitionLogicalCorrelationIds; if (!flag2) { recognitionLogicalCorrelationIds = state.RecognitionLogicalCorrelationIds; } else { IReadOnlyList readOnlyList = state.RecognitionLogicalCorrelationIds.Append(element).ToArray(); recognitionLogicalCorrelationIds = readOnlyList; } obj2.RecognitionLogicalCorrelationIds = recognitionLogicalCorrelationIds; IReadOnlyList nativeEffects; if ((object)command.PreparedNativeEffect != null) { IReadOnlyList readOnlyList2 = state.NativeEffects.Append(command.PreparedNativeEffect with { PreparedStoryRevision = state.Revision + 1, AuthorizedStoryCorrelationId = command.CorrelationId, AuthorizedMissionRevision = mission.Revision + 1 }).ToArray(); nativeEffects = readOnlyList2; } else { nativeEffects = state.NativeEffects; } obj2.NativeEffects = nativeEffects; Release1StoryState release1StoryState = obj2; if (!flag2 && state.Missions[index + 1].State == Release1MissionState.Locked) { release1StoryState = release1StoryState with { Missions = state.Missions.Select((Release1MissionRecord m, int i) => (i != index + 1) ? m : m with { State = Release1MissionState.Offered, Revision = m.Revision + 1 }).ToArray() }; } return ChangeMission(release1StoryState, index, mission2); } private static Release1StoryTransitionResult Reoffer(Release1StoryState state, int index, Release1MissionRecord mission, Release1StoryCommand command) { if (mission.State != Release1MissionState.Deferred) { throw WrongState(); } return ChangeMission(state, index, mission with { State = Release1MissionState.Offered, Attempt = mission.Attempt + 1, TermsVersion = null, AcceptedGameTimeHours = null, DeadlineGameTimeHours = null, LastOutcome = Release1MissionOutcome.None, RecoveryMode = Release1RecoveryMode.None }, command.CorrelationId); } private static Release1StoryTransitionResult ChangeMission(Release1StoryState state, int index, Release1MissionRecord mission, string? correlationId = null) { long revision = state.Revision + 1; if (correlationId != null && !mission.AcceptedLogicalCorrelations.Contains(correlationId, StringComparer.Ordinal)) { mission = mission with { AcceptedLogicalCorrelations = mission.AcceptedLogicalCorrelations.Append(correlationId).ToArray() }; } Release1MissionRecord nextMission = mission with { Revision = mission.Revision + 1 }; Release1MissionRecord[] missions = state.Missions.Select((Release1MissionRecord current, int i) => (i != index) ? current : nextMission).ToArray(); return Release1StoryTransitionResult.AcceptedState(state with { Missions = missions, Revision = revision }); } private static Release1MissionRecord Require(Release1MissionRecord mission, Release1MissionState expected) { if (mission.State != expected) { throw WrongState(); } return mission; } private static TransitionException WrongState() { return new TransitionException(Release1StoryTransitionRejectReason.WrongState, "Mission state does not allow this transition."); } private static void ValidateTimes(double? accepted, double? deadline) { if ((accepted.HasValue && (double.IsNaN(accepted.Value) || double.IsInfinity(accepted.Value) || accepted < 0.0)) || (deadline.HasValue && (double.IsNaN(deadline.Value) || double.IsInfinity(deadline.Value) || deadline < 0.0)) || (accepted.HasValue && deadline.HasValue && deadline < accepted)) { throw new TransitionException(Release1StoryTransitionRejectReason.InvalidTime, "Mission times must be finite, non-negative, and ordered."); } } } public enum Release1TheEnvelopeAssignmentMode { Primary, MakeGood, Recovery } public enum Release1TheEnvelopeSelectionStatus { Selected, InvalidInput } public sealed record Release1TheEnvelopeAssignment { public string MissionKey { get { return _missionKey; } init { if (!string.Equals(value, "release1.the-envelope", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be The Envelope.", "MissionKey"); } _missionKey = value; } } public int Attempt { get { return _attempt; } init { if (value < 1) { throw new ArgumentOutOfRangeException("Attempt"); } _attempt = value; ValidateAuthorizationIfPresent(); } } public Release1TheEnvelopeAssignmentMode Mode { get { return _mode; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } _mode = value; ValidateAuthorizationIfPresent(); } } public string AuthorizationCorrelationId { get { return _authorizationCorrelationId; } init { ValidateAuthorization(value, Attempt, Mode); _authorizationCorrelationId = value; } } public double AmountWholeDollars { get { return _amountWholeDollars; } init { if (value != AmountFor(Mode)) { throw new ArgumentOutOfRangeException("AmountWholeDollars", "The Envelope amount must match the stage."); } _amountWholeDollars = value; } } public string HoldRoomKey { get { return _holdRoomKey; } init { if (!string.Equals(value, "syndicate-hq", StringComparison.Ordinal)) { throw new ArgumentException("The hold room is always the Syndicate HQ storage room.", "HoldRoomKey"); } _holdRoomKey = value; } } public int ExpectedClosetCount { get { return _expectedClosetCount; } init { if (value != 9) { throw new ArgumentOutOfRangeException("ExpectedClosetCount", "The hold room has exactly nine closets."); } _expectedClosetCount = value; } } public double? DeadlineGameMinutes { get { return _deadlineGameMinutes; } init { ValidateDeadline(value, Mode); _deadlineGameMinutes = value; } } public const double PrimaryAmount = 20000.0; public const double MakeGoodAmount = 10000.0; public const double RecoveryAmount = 5000.0; public const double TimedStageGameMinutes = 1440.0; private string _missionKey = string.Empty; private int _attempt; private Release1TheEnvelopeAssignmentMode _mode; private string _authorizationCorrelationId = string.Empty; private double _amountWholeDollars; private string _holdRoomKey = string.Empty; private int _expectedClosetCount; private double? _deadlineGameMinutes; public Release1TheEnvelopeAssignment(string MissionKey, int Attempt, Release1TheEnvelopeAssignmentMode Mode, string AuthorizationCorrelationId, double AmountWholeDollars, string HoldRoomKey, int ExpectedClosetCount, double? DeadlineGameMinutes) { this.MissionKey = MissionKey; this.Attempt = Attempt; this.Mode = Mode; this.AuthorizationCorrelationId = AuthorizationCorrelationId; this.AmountWholeDollars = AmountWholeDollars; this.HoldRoomKey = HoldRoomKey; this.ExpectedClosetCount = ExpectedClosetCount; this.DeadlineGameMinutes = DeadlineGameMinutes; Validate(); } public static double AmountFor(Release1TheEnvelopeAssignmentMode mode) { return mode switch { Release1TheEnvelopeAssignmentMode.Primary => 20000.0, Release1TheEnvelopeAssignmentMode.MakeGood => 10000.0, Release1TheEnvelopeAssignmentMode.Recovery => 5000.0, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; } public void Validate() { if (!string.Equals(MissionKey, "release1.the-envelope", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be The Envelope.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!Enum.IsDefined(Mode)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } ValidateAuthorization(AuthorizationCorrelationId, Attempt, Mode); if (AmountWholeDollars != AmountFor(Mode)) { throw new ArgumentOutOfRangeException("AmountWholeDollars"); } if (!string.Equals(HoldRoomKey, "syndicate-hq", StringComparison.Ordinal)) { throw new ArgumentException("The hold room is always the Syndicate HQ storage room.", "HoldRoomKey"); } if (ExpectedClosetCount != 9) { throw new ArgumentOutOfRangeException("ExpectedClosetCount"); } ValidateDeadline(DeadlineGameMinutes, Mode); } private static void ValidateDeadline(double? value, Release1TheEnvelopeAssignmentMode mode) { if (mode == Release1TheEnvelopeAssignmentMode.Recovery) { if (value.HasValue) { throw new ArgumentException("Recovery has no deadline.", "DeadlineGameMinutes"); } } else if (!value.HasValue || !double.IsFinite(value.Value) || value.Value != 1440.0) { throw new ArgumentOutOfRangeException("DeadlineGameMinutes", "Timed Envelope stages run for 1440 game minutes."); } } private void ValidateAuthorizationIfPresent() { if (_authorizationCorrelationId.Length != 0) { ValidateAuthorization(_authorizationCorrelationId, Attempt, Mode); } } private static void ValidateAuthorization(string? value, int attempt, Release1TheEnvelopeAssignmentMode mode) { Release1TransitionKind release1TransitionKind = mode switch { Release1TheEnvelopeAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1TheEnvelopeAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1TheEnvelopeAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; if (!Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.the-envelope" || correlation.Attempt != attempt || correlation.TransitionKind != release1TransitionKind) { throw new ArgumentException("Assignment authorization correlation did not match mission, attempt, and mode.", "AuthorizationCorrelationId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", Mode = "); builder.Append(Mode.ToString()); builder.Append(", AuthorizationCorrelationId = "); builder.Append((object?)AuthorizationCorrelationId); builder.Append(", AmountWholeDollars = "); builder.Append(AmountWholeDollars.ToString()); builder.Append(", HoldRoomKey = "); builder.Append((object?)HoldRoomKey); builder.Append(", ExpectedClosetCount = "); builder.Append(ExpectedClosetCount.ToString()); builder.Append(", DeadlineGameMinutes = "); builder.Append(DeadlineGameMinutes.ToString()); return true; } [CompilerGenerated] private Release1TheEnvelopeAssignment(Release1TheEnvelopeAssignment original) { _missionKey = original._missionKey; _attempt = original._attempt; _mode = original._mode; _authorizationCorrelationId = original._authorizationCorrelationId; _amountWholeDollars = original._amountWholeDollars; _holdRoomKey = original._holdRoomKey; _expectedClosetCount = original._expectedClosetCount; _deadlineGameMinutes = original._deadlineGameMinutes; } } public sealed record Release1TheEnvelopeProgress(string MissionKey, int Attempt, double ObservedBalance, double? LastShortfallNoticed, bool SpreadNoticed) { public static Release1TheEnvelopeProgress Fresh(int attempt) { return new Release1TheEnvelopeProgress("release1.the-envelope", attempt, 0.0, null, SpreadNoticed: false); } public void Validate() { if (!string.Equals(MissionKey, "release1.the-envelope", StringComparison.Ordinal)) { throw new ArgumentException("Progress mission must be The Envelope.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!double.IsFinite(ObservedBalance) || ObservedBalance < 0.0) { throw new ArgumentOutOfRangeException("ObservedBalance"); } if (LastShortfallNoticed.HasValue && (!double.IsFinite(LastShortfallNoticed.Value) || LastShortfallNoticed.Value <= 0.0)) { throw new ArgumentOutOfRangeException("LastShortfallNoticed", "A noticed shortfall is a positive dollar amount."); } } } public static class Release1TheEnvelopeAssignmentSelector { public static bool TrySelect(Release1TheEnvelopeAssignmentMode mode, int attempt, string? authorizationCorrelationId, out Release1TheEnvelopeAssignment? assignment, out Release1TheEnvelopeSelectionStatus status) { assignment = null; status = Release1TheEnvelopeSelectionStatus.InvalidInput; try { assignment = new Release1TheEnvelopeAssignment("release1.the-envelope", attempt, mode, authorizationCorrelationId, Release1TheEnvelopeAssignment.AmountFor(mode), "syndicate-hq", 9, (mode == Release1TheEnvelopeAssignmentMode.Recovery) ? ((double?)null) : new double?(1440.0)); status = Release1TheEnvelopeSelectionStatus.Selected; return true; } catch (ArgumentException) { assignment = null; status = Release1TheEnvelopeSelectionStatus.InvalidInput; return false; } } } public sealed record Release1TheEnvelopePlannedSlot(int SlotIndex, double PreBalance, double PostBalance); public sealed record Release1TheEnvelopeCashSlot(int SlotIndex, double Balance); public sealed record Release1TheEnvelopeClosetPlan { public string ClosetGuid { get; } public IReadOnlyList Slots { get; } public double ConsumedAmount { get; } private const string Prefix = "te-closet-v1"; private const char GroupDelimiter = '|'; private const char FieldDelimiter = ','; public const int MaximumEncodedLength = 256; public const double WholeDollarTolerance = 0.01; public Release1TheEnvelopeClosetPlan(string closetGuid, IReadOnlyList slots) { Release1SmallCourtesyAssignment.ValidateStableId(closetGuid, "closetGuid"); ArgumentNullException.ThrowIfNull(slots, "slots"); if (slots.Count == 0) { throw new ArgumentException("A plan touches at least one slot.", "slots"); } int num = -1; double num2 = 0.0; for (int i = 0; i < slots.Count; i++) { Release1TheEnvelopePlannedSlot release1TheEnvelopePlannedSlot = slots[i] ?? throw new ArgumentException("Planned slots cannot be null.", "slots"); if (release1TheEnvelopePlannedSlot.SlotIndex < 0 || release1TheEnvelopePlannedSlot.SlotIndex <= num) { throw new ArgumentException("Planned slots are strictly ascending by slot index.", "slots"); } num = release1TheEnvelopePlannedSlot.SlotIndex; if (!double.IsFinite(release1TheEnvelopePlannedSlot.PreBalance) || release1TheEnvelopePlannedSlot.PreBalance <= 0.0 || release1TheEnvelopePlannedSlot.PreBalance != Math.Truncate(release1TheEnvelopePlannedSlot.PreBalance) || !double.IsFinite(release1TheEnvelopePlannedSlot.PostBalance) || release1TheEnvelopePlannedSlot.PostBalance < 0.0 || release1TheEnvelopePlannedSlot.PostBalance >= release1TheEnvelopePlannedSlot.PreBalance || release1TheEnvelopePlannedSlot.PostBalance != Math.Truncate(release1TheEnvelopePlannedSlot.PostBalance)) { throw new ArgumentException("Every planned balance is a whole dollar value, pre positive and post below it.", "slots"); } if (i < slots.Count - 1 && release1TheEnvelopePlannedSlot.PostBalance != 0.0) { throw new ArgumentException("Only the last planned slot may keep a remainder.", "slots"); } num2 += release1TheEnvelopePlannedSlot.PreBalance - release1TheEnvelopePlannedSlot.PostBalance; } ClosetGuid = closetGuid; Slots = slots.ToArray(); ConsumedAmount = num2; if (BuildEncoded().Length > 256) { throw new ArgumentException("Closet plan exceeds the native effect field limit.", "slots"); } } public string Serialize() { return BuildEncoded(); } public static bool TryBuild(IReadOnlyList? cashSlots, string closetGuid, double amountWholeDollars, out Release1TheEnvelopeClosetPlan? plan) { plan = null; if (cashSlots == null || cashSlots.Count == 0) { return false; } if (!double.IsFinite(amountWholeDollars) || amountWholeDollars <= 0.0 || amountWholeDollars != Math.Truncate(amountWholeDollars)) { return false; } List list = new List(); double num = amountWholeDollars; foreach (Release1TheEnvelopeCashSlot item in from candidate in cashSlots where (object)candidate != null orderby candidate.SlotIndex select candidate) { if (num <= 0.0) { break; } if (!double.IsFinite(item.Balance) || item.Balance <= 0.0) { return false; } double num2 = Math.Round(item.Balance, MidpointRounding.AwayFromZero); if (Math.Abs(item.Balance - num2) > 0.01 || num2 <= 0.0) { return false; } double num3 = Math.Min(num2, num); list.Add(new Release1TheEnvelopePlannedSlot(item.SlotIndex, num2, num2 - num3)); num -= num3; } if (num > 0.0) { return false; } try { plan = new Release1TheEnvelopeClosetPlan(closetGuid, list); return true; } catch (ArgumentException) { plan = null; return false; } } public static bool TryParse(string? encoded, string closetGuid, out Release1TheEnvelopeClosetPlan? plan) { plan = null; if (string.IsNullOrEmpty(encoded) || encoded.Length > 256) { return false; } string[] array = encoded.Split('|'); if (array.Length < 2 || !string.Equals(array[0], "te-closet-v1", StringComparison.Ordinal)) { return false; } List list = new List(); for (int i = 1; i < array.Length; i++) { string[] array2 = array[i].Split(','); if (array2.Length != 3 || !int.TryParse(array2[0], NumberStyles.None, CultureInfo.InvariantCulture, out var result) || !double.TryParse(array2[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var result2) || !double.TryParse(array2[2], NumberStyles.Float, CultureInfo.InvariantCulture, out var result3)) { return false; } list.Add(new Release1TheEnvelopePlannedSlot(result, result2, result3)); } try { plan = new Release1TheEnvelopeClosetPlan(closetGuid, list); } catch (ArgumentException) { return false; } if (string.Equals(plan.Serialize(), encoded, StringComparison.Ordinal)) { return true; } plan = null; return false; } private string BuildEncoded() { return string.Join('|', new string[1] { "te-closet-v1" }.Concat(Slots.Select((Release1TheEnvelopePlannedSlot slot) => string.Join(',', slot.SlotIndex.ToString(CultureInfo.InvariantCulture), slot.PreBalance.ToString("0", CultureInfo.InvariantCulture), slot.PostBalance.ToString("0", CultureInfo.InvariantCulture))))); } [CompilerGenerated] private Release1TheEnvelopeClosetPlan(Release1TheEnvelopeClosetPlan original) { ClosetGuid = original.ClosetGuid; Slots = original.Slots; ConsumedAmount = original.ConsumedAmount; } } public sealed record Release1TheEnvelopeClosetCash(string ClosetGuid, bool Readable, IReadOnlyList CashSlots); public enum Release1TheEnvelopeClosetObservation { RoomNotReady, ClosetUnreadable, NoCash, Shortfall, Sufficient, Spread } public static class Release1TheEnvelopeClosetClassifier { public static Release1TheEnvelopeClosetObservation Classify(Release1HoldRoomSnapshot? room, IReadOnlyList? closets, int expectedClosetCount, double amountWholeDollars, out string? cashClosetGuid, out double cashSum) { cashClosetGuid = null; cashSum = 0.0; if (expectedClosetCount < 1) { throw new ArgumentOutOfRangeException("expectedClosetCount"); } if (!double.IsFinite(amountWholeDollars) || amountWholeDollars <= 0.0) { throw new ArgumentOutOfRangeException("amountWholeDollars"); } if ((object)room == null || room.Readiness != Release1HoldRoomReadiness.Ready || room.Closets.Count != expectedClosetCount) { return Release1TheEnvelopeClosetObservation.RoomNotReady; } if (closets == null || closets.Count != expectedClosetCount) { return Release1TheEnvelopeClosetObservation.RoomNotReady; } foreach (Release1HoldRoomClosetSnapshot closet in room.Closets) { if ((object)closet == null || closet.SlotCount == 0 || closet.Slots.Count == 0) { return Release1TheEnvelopeClosetObservation.ClosetUnreadable; } } foreach (Release1TheEnvelopeClosetCash closet2 in closets) { if ((object)closet2 == null || !closet2.Readable || closet2.CashSlots == null) { return Release1TheEnvelopeClosetObservation.ClosetUnreadable; } } Release1TheEnvelopeClosetCash[] array = closets.Where((Release1TheEnvelopeClosetCash closet) => closet.CashSlots.Any((Release1TheEnvelopeCashSlot slot) => (object)slot != null && slot.Balance > 0.0)).OrderBy((Release1TheEnvelopeClosetCash closet) => closet.ClosetGuid, StringComparer.Ordinal).ToArray(); if (array.Length == 0) { return Release1TheEnvelopeClosetObservation.NoCash; } if (array.Length > 1) { return Release1TheEnvelopeClosetObservation.Spread; } cashClosetGuid = array[0].ClosetGuid; cashSum = array[0].CashSlots.Where((Release1TheEnvelopeCashSlot slot) => (object)slot != null).Sum((Release1TheEnvelopeCashSlot slot) => slot.Balance); if (!(cashSum >= amountWholeDollars)) { return Release1TheEnvelopeClosetObservation.Shortfall; } return Release1TheEnvelopeClosetObservation.Sufficient; } } public enum Release1WrongAddressAssignmentMode { Primary, MakeGood, Recovery } public enum Release1WrongAddressSelectionStatus { Selected, NoDiscoveredProduct, InsufficientEmptyDeadDrops, InvalidInput } public sealed record Release1WrongAddressAssignment { public string MissionKey { get { return _missionKey; } init { if (!string.Equals(value, "release1.wrong-address", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Wrong Address.", "MissionKey"); } _missionKey = value; } } public int Attempt { get { return _attempt; } init { if (value < 1) { throw new ArgumentOutOfRangeException("Attempt"); } _attempt = value; ValidateAuthorizationIfPresent(); } } public Release1WrongAddressAssignmentMode Mode { get { return _mode; } init { if (!Enum.IsDefined(value)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } _mode = value; ValidateAuthorizationIfPresent(); } } public string AuthorizationCorrelationId { get { return _authorizationCorrelationId; } init { ValidateAuthorization(value, Attempt, Mode); _authorizationCorrelationId = value; } } public string ProductId { get { return _productId; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "ProductId"); _productId = value; } } public string ProductName { get { return _productName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "ProductName"); _productName = value; } } public string PackagingId { get { return _packagingId; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "PackagingId"); _packagingId = value; } } public string PackagingName { get { return _packagingName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "PackagingName"); _packagingName = value; } } public int PackageQuantity { get { return _packageQuantity; } init { if (value != 1) { throw new ArgumentOutOfRangeException("PackageQuantity", "Wrong Address packages are exactly one unit."); } _packageQuantity = value; } } public string SourceDropGuid { get { return _sourceDropGuid; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "SourceDropGuid"); _sourceDropGuid = value; } } public string SourceDropName { get { return _sourceDropName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "SourceDropName"); _sourceDropName = value; } } public string SourceDropDescription { get { return _sourceDropDescription; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 1024, "SourceDropDescription"); _sourceDropDescription = value; } } public double SourceDropX { get { return _sourceDropX; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "SourceDropX"); _sourceDropX = value; } } public double SourceDropY { get { return _sourceDropY; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "SourceDropY"); _sourceDropY = value; } } public double SourceDropZ { get { return _sourceDropZ; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "SourceDropZ"); _sourceDropZ = value; } } public string HandoffDropGuid { get { return _handoffDropGuid; } init { Release1SmallCourtesyAssignment.ValidateStableId(value, "HandoffDropGuid"); _handoffDropGuid = value; } } public string HandoffDropName { get { return _handoffDropName; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 256, "HandoffDropName"); _handoffDropName = value; } } public string HandoffDropDescription { get { return _handoffDropDescription; } init { Release1SmallCourtesyAssignment.ValidatePresentation(value, 1024, "HandoffDropDescription"); _handoffDropDescription = value; } } public double HandoffDropX { get { return _handoffDropX; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropX"); _handoffDropX = value; } } public double HandoffDropY { get { return _handoffDropY; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropY"); _handoffDropY = value; } } public double HandoffDropZ { get { return _handoffDropZ; } init { Release1SmallCourtesyAssignment.ValidateFinite(value, "HandoffDropZ"); _handoffDropZ = value; } } public double RewardMultiplier { get { return _rewardMultiplier; } init { if (!double.IsFinite(value) || value != 1.25) { throw new ArgumentOutOfRangeException("RewardMultiplier", "Wrong Address reward multiplier must be 1.25."); } _rewardMultiplier = value; } } private string _missionKey = string.Empty; private int _attempt; private Release1WrongAddressAssignmentMode _mode; private string _authorizationCorrelationId = string.Empty; private string _productId = string.Empty; private string _productName = string.Empty; private string _packagingId = string.Empty; private string _packagingName = string.Empty; private int _packageQuantity; private string _sourceDropGuid = string.Empty; private string _sourceDropName = string.Empty; private string _sourceDropDescription = string.Empty; private double _sourceDropX; private double _sourceDropY; private double _sourceDropZ; private string _handoffDropGuid = string.Empty; private string _handoffDropName = string.Empty; private string _handoffDropDescription = string.Empty; private double _handoffDropX; private double _handoffDropY; private double _handoffDropZ; private double _rewardMultiplier; public Release1WrongAddressAssignment(string MissionKey, int Attempt, Release1WrongAddressAssignmentMode Mode, string AuthorizationCorrelationId, string ProductId, string ProductName, string PackagingId, string PackagingName, int PackageQuantity, string SourceDropGuid, string SourceDropName, string SourceDropDescription, double SourceDropX, double SourceDropY, double SourceDropZ, string HandoffDropGuid, string HandoffDropName, string HandoffDropDescription, double HandoffDropX, double HandoffDropY, double HandoffDropZ, double RewardMultiplier) { this.MissionKey = MissionKey; this.Attempt = Attempt; this.Mode = Mode; this.AuthorizationCorrelationId = AuthorizationCorrelationId; this.ProductId = ProductId; this.ProductName = ProductName; this.PackagingId = PackagingId; this.PackagingName = PackagingName; this.PackageQuantity = PackageQuantity; this.SourceDropGuid = SourceDropGuid; this.SourceDropName = SourceDropName; this.SourceDropDescription = SourceDropDescription; this.SourceDropX = SourceDropX; this.SourceDropY = SourceDropY; this.SourceDropZ = SourceDropZ; this.HandoffDropGuid = HandoffDropGuid; this.HandoffDropName = HandoffDropName; this.HandoffDropDescription = HandoffDropDescription; this.HandoffDropX = HandoffDropX; this.HandoffDropY = HandoffDropY; this.HandoffDropZ = HandoffDropZ; this.RewardMultiplier = RewardMultiplier; Validate(); } public void Validate() { if (!string.Equals(MissionKey, "release1.wrong-address", StringComparison.Ordinal)) { throw new ArgumentException("Assignment mission must be Wrong Address.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (!Enum.IsDefined(Mode)) { throw new ArgumentException("Assignment mode is not defined.", "Mode"); } ValidateAuthorization(AuthorizationCorrelationId, Attempt, Mode); Release1SmallCourtesyAssignment.ValidateStableId(ProductId, "ProductId"); Release1SmallCourtesyAssignment.ValidatePresentation(ProductName, 256, "ProductName"); Release1SmallCourtesyAssignment.ValidateStableId(PackagingId, "PackagingId"); Release1SmallCourtesyAssignment.ValidatePresentation(PackagingName, 256, "PackagingName"); if (PackageQuantity != 1) { throw new ArgumentOutOfRangeException("PackageQuantity"); } Release1SmallCourtesyAssignment.ValidateStableId(SourceDropGuid, "SourceDropGuid"); Release1SmallCourtesyAssignment.ValidatePresentation(SourceDropName, 256, "SourceDropName"); Release1SmallCourtesyAssignment.ValidatePresentation(SourceDropDescription, 1024, "SourceDropDescription"); Release1SmallCourtesyAssignment.ValidateFinite(SourceDropX, "SourceDropX"); Release1SmallCourtesyAssignment.ValidateFinite(SourceDropY, "SourceDropY"); Release1SmallCourtesyAssignment.ValidateFinite(SourceDropZ, "SourceDropZ"); Release1SmallCourtesyAssignment.ValidateStableId(HandoffDropGuid, "HandoffDropGuid"); Release1SmallCourtesyAssignment.ValidatePresentation(HandoffDropName, 256, "HandoffDropName"); Release1SmallCourtesyAssignment.ValidatePresentation(HandoffDropDescription, 1024, "HandoffDropDescription"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropX, "HandoffDropX"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropY, "HandoffDropY"); Release1SmallCourtesyAssignment.ValidateFinite(HandoffDropZ, "HandoffDropZ"); if (string.Equals(SourceDropGuid, HandoffDropGuid, StringComparison.Ordinal)) { throw new ArgumentException("The wrong address and the right address must be different drops.", "HandoffDropGuid"); } if (!double.IsFinite(RewardMultiplier) || RewardMultiplier != 1.25) { throw new ArgumentOutOfRangeException("RewardMultiplier"); } } private void ValidateAuthorizationIfPresent() { if (_authorizationCorrelationId.Length != 0) { ValidateAuthorization(_authorizationCorrelationId, Attempt, Mode); } } private static void ValidateAuthorization(string? value, int attempt, Release1WrongAddressAssignmentMode mode) { Release1TransitionKind release1TransitionKind = mode switch { Release1WrongAddressAssignmentMode.Primary => Release1TransitionKind.MissionAccepted, Release1WrongAddressAssignmentMode.MakeGood => Release1TransitionKind.MakeGoodAccepted, Release1WrongAddressAssignmentMode.Recovery => Release1TransitionKind.RecoveryAccepted, _ => throw new ArgumentException("Assignment mode is not defined.", "mode"), }; if (!Release1LogicalCorrelation.TryParse(value, out Release1LogicalCorrelation correlation) || correlation.MissionKey != "release1.wrong-address" || correlation.Attempt != attempt || correlation.TransitionKind != release1TransitionKind) { throw new ArgumentException("Assignment authorization correlation did not match mission, attempt, and mode.", "AuthorizationCorrelationId"); } } [CompilerGenerated] private bool PrintMembers(StringBuilder builder) { RuntimeHelpers.EnsureSufficientExecutionStack(); builder.Append("MissionKey = "); builder.Append((object?)MissionKey); builder.Append(", Attempt = "); builder.Append(Attempt.ToString()); builder.Append(", Mode = "); builder.Append(Mode.ToString()); builder.Append(", AuthorizationCorrelationId = "); builder.Append((object?)AuthorizationCorrelationId); builder.Append(", ProductId = "); builder.Append((object?)ProductId); builder.Append(", ProductName = "); builder.Append((object?)ProductName); builder.Append(", PackagingId = "); builder.Append((object?)PackagingId); builder.Append(", PackagingName = "); builder.Append((object?)PackagingName); builder.Append(", PackageQuantity = "); builder.Append(PackageQuantity.ToString()); builder.Append(", SourceDropGuid = "); builder.Append((object?)SourceDropGuid); builder.Append(", SourceDropName = "); builder.Append((object?)SourceDropName); builder.Append(", SourceDropDescription = "); builder.Append((object?)SourceDropDescription); builder.Append(", SourceDropX = "); builder.Append(SourceDropX.ToString()); builder.Append(", SourceDropY = "); builder.Append(SourceDropY.ToString()); builder.Append(", SourceDropZ = "); builder.Append(SourceDropZ.ToString()); builder.Append(", HandoffDropGuid = "); builder.Append((object?)HandoffDropGuid); builder.Append(", HandoffDropName = "); builder.Append((object?)HandoffDropName); builder.Append(", HandoffDropDescription = "); builder.Append((object?)HandoffDropDescription); builder.Append(", HandoffDropX = "); builder.Append(HandoffDropX.ToString()); builder.Append(", HandoffDropY = "); builder.Append(HandoffDropY.ToString()); builder.Append(", HandoffDropZ = "); builder.Append(HandoffDropZ.ToString()); builder.Append(", RewardMultiplier = "); builder.Append(RewardMultiplier.ToString()); return true; } } public sealed record Release1WrongAddressProgress(string MissionKey, int Attempt, bool Staged, bool Custody) { public void Validate() { if (!string.Equals(MissionKey, "release1.wrong-address", StringComparison.Ordinal)) { throw new ArgumentException("Progress mission must be Wrong Address.", "MissionKey"); } if (Attempt < 1) { throw new ArgumentOutOfRangeException("Attempt"); } if (Custody && !Staged) { throw new ArgumentException("Custody cannot be recorded before the package was staged.", "Custody"); } } } public static class Release1WrongAddressAssignmentSelector { public static bool TrySelect(IReadOnlyList? products, IReadOnlyList? drops, Release1WrongAddressAssignmentMode mode, int attempt, string? authorizationCorrelationId, string? packagingId, string? packagingName, out Release1WrongAddressAssignment? assignment, out Release1WrongAddressSelectionStatus status) { assignment = null; status = Release1WrongAddressSelectionStatus.InvalidInput; if (products == null || drops == null) { return false; } try { foreach (Release1SmallCourtesyProductCandidate product in products) { (product ?? throw new ArgumentException("Product candidates cannot be null.")).Validate(); } foreach (Release1SmallCourtesyDropCandidate drop in drops) { (drop ?? throw new ArgumentException("Drop candidates cannot be null.")).Validate(); } Release1SmallCourtesyProductCandidate release1SmallCourtesyProductCandidate = (from candidate in products where candidate.IsDiscovered orderby candidate.AskingPrice descending select candidate).ThenBy((Release1SmallCourtesyProductCandidate candidate) => candidate.ProductId, StringComparer.Ordinal).FirstOrDefault(); if ((object)release1SmallCourtesyProductCandidate == null) { status = Release1WrongAddressSelectionStatus.NoDiscoveredProduct; return false; } if (!Release1DeadDropSelection.TryChoosePair(drops, authorizationCorrelationId, out Release1SmallCourtesyDropCandidate source, out Release1SmallCourtesyDropCandidate handoff)) { status = Release1WrongAddressSelectionStatus.InsufficientEmptyDeadDrops; return false; } assignment = new Release1WrongAddressAssignment("release1.wrong-address", attempt, mode, authorizationCorrelationId, release1SmallCourtesyProductCandidate.ProductId, release1SmallCourtesyProductCandidate.ProductName, packagingId, packagingName, 1, source.Guid, source.Name, source.Description, source.X, source.Y, source.Z, handoff.Guid, handoff.Name, handoff.Description, handoff.X, handoff.Y, handoff.Z, 1.25); status = Release1WrongAddressSelectionStatus.Selected; return true; } catch (ArgumentException) { assignment = null; status = Release1WrongAddressSelectionStatus.InvalidInput; return false; } } } public sealed record RuntimePropertyDefinition(string PropertyCode, string DisplayName, string NativeName, string AnchorPath, string RootName, ushort RuntimeCollectionId, string BuildGridRootName, string BuildGridGuid) { public static RuntimePropertyDefinition FishWarehouse { get; } = new RuntimePropertyDefinition("oc_fishwarehouse", "Syndicate Warehouse", "Fish Warehouse", "Map/Hyland Point/Region_Docks/Fish Warehouse/fishwarehouse", "OC_FishWarehouse_PropertyRoot", 65000, "OC_FishWarehouse_BuildGrid", "d8b7e1d4-3f1d-4cf0-9b7f-2e2fc8d6c6d1"); public bool HasBuildGridGuid(string? gridGuid) { if (!string.IsNullOrWhiteSpace(gridGuid)) { return string.Equals(BuildGridGuid, gridGuid, StringComparison.OrdinalIgnoreCase); } return false; } [CompilerGenerated] private RuntimePropertyDefinition(RuntimePropertyDefinition original) { PropertyCode = original.PropertyCode; DisplayName = original.DisplayName; NativeName = original.NativeName; AnchorPath = original.AnchorPath; RootName = original.RootName; RuntimeCollectionId = original.RuntimeCollectionId; BuildGridRootName = original.BuildGridRootName; BuildGridGuid = original.BuildGridGuid; } } public sealed record FishWarehouseSavedObjectDescriptor(int PayloadIndex, string DataType, string Guid, int SavedLoadOrder, bool IsDependent, bool IsSavedEmployeeHome); public static class SyndicateHqContract { public const string RootName = "OC_SyndicateHQ_Interior"; public const string EntryMarker = "HQ_ENTRY"; public const string ExitMarker = "HQ_EXIT"; public const string StorageMarker = "HQ_STORAGE"; public const string EnterAction = "Enter Syndicate HQ"; public const string ExitAction = "Exit Syndicate HQ"; public const string NightclubShellPath = "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall"; public const string NightclubDoorPath = "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall/Facade (1)/Metal Glass Door (Static)"; public const string NightclubBuildingPath = "Map/Hyland Point/Region_Northtown/Nightclub"; public const string NightclubBuildingGuid = "6121cc6b-ccc6-4c9e-b4cb-2d2db634f671"; public const int NightclubDoorIndex = 0; public const float PositionTolerance = 0.1f; public const float ForwardDotTolerance = 0.999f; } public readonly record struct SyndicateHqVector3(float X, float Y, float Z) { public bool IsFinite { get { if (!float.IsNaN(X) && !float.IsInfinity(X) && !float.IsNaN(Y) && !float.IsInfinity(Y) && !float.IsNaN(Z)) { return !float.IsInfinity(Z); } return false; } } public float LengthSquared => X * X + Y * Y + Z * Z; public bool IsNonZero { get { if (IsFinite) { return LengthSquared > 1E-06f; } return false; } } public static SyndicateHqVector3 Zero => new SyndicateHqVector3(0f, 0f, 0f); public SyndicateHqVector3 Normalized() { if (!IsNonZero) { return Zero; } float num = MathF.Sqrt(LengthSquared); return new SyndicateHqVector3(X / num, Y / num, Z / num); } public float NormalizedDot(SyndicateHqVector3 other) { return Normalized().X * other.Normalized().X + Normalized().Y * other.Normalized().Y + Normalized().Z * other.Normalized().Z; } public float DistanceTo(SyndicateHqVector3 other) { float num = X - other.X; float num2 = Y - other.Y; float num3 = Z - other.Z; return MathF.Sqrt(num * num + num2 * num2 + num3 * num3); } public static SyndicateHqVector3 operator +(SyndicateHqVector3 left, SyndicateHqVector3 right) { return new SyndicateHqVector3(left.X + right.X, left.Y + right.Y, left.Z + right.Z); } public static SyndicateHqVector3 operator *(SyndicateHqVector3 value, float scalar) { return new SyndicateHqVector3(value.X * scalar, value.Y * scalar, value.Z * scalar); } } public readonly record struct SyndicateHqQuaternion(float X, float Y, float Z, float W) { public static SyndicateHqQuaternion Identity => new SyndicateHqQuaternion(0f, 0f, 0f, 1f); } public sealed record SyndicateHqDoorFingerprint(string DoorPath, string DoorType, string InteractablePath, string InteractableType, string BuildingPath, string BuildingType, string BuildingGuid, string AccessPointPath, int DoorIndex, SyndicateHqVector3 Position, SyndicateHqVector3 Forward, SyndicateHqVector3? AccessPointPosition, bool HasIntObj, bool HasBuilding, bool HasAccessPoint, bool HasCanKnock, bool HasUsable); public sealed record SyndicateHqSceneSnapshot(IReadOnlyList ShellPaths, IReadOnlyList Doors); public sealed record SyndicateHqIdentityCheckResult(bool Accepted, string Reason, SyndicateHqDoorFingerprint? Door) { public static SyndicateHqIdentityCheckResult Reject(string reason) { return new SyndicateHqIdentityCheckResult(Accepted: false, reason, null); } public static SyndicateHqIdentityCheckResult Accept(SyndicateHqDoorFingerprint door) { return new SyndicateHqIdentityCheckResult(Accepted: true, "Exact Nightclub shell and door fingerprint matched.", door); } } public sealed record SyndicateHqExteriorReturnState(SyndicateHqVector3? CapturedPosition, SyndicateHqVector3? AccessPointPosition, Guid SessionEpoch, long LoadEpoch, string CanonicalPlayerId); public enum SyndicateHqReturnPath { NoSafeReturn, CapturedExteriorPosition, DoorAccessPoint } public sealed record SyndicateHqReturnPlan(SyndicateHqReturnPath Path, SyndicateHqVector3 Position, string Reason); public enum SyndicateHqPromptKind { None, Locked, Enter, Exit, Fault } public sealed record SyndicateHqPrompt(SyndicateHqPromptKind Kind, string Text, string ActionText); public enum SyndicateHqEntryState { Detached, AwaitingDoor, LockedPrompt, EnterPrompt, Entering, Inside, Exiting, Faulted, Disposed } public sealed class SyndicateHqEntryStateMachine { public SyndicateHqEntryState State { get; private set; } public bool MarkReady() { if (State == SyndicateHqEntryState.Disposed) { return false; } SyndicateHqEntryState state = State; if ((state == SyndicateHqEntryState.Detached || state == SyndicateHqEntryState.Faulted) ? true : false) { State = SyndicateHqEntryState.AwaitingDoor; } return true; } public bool ObserveDoor(bool unlocked) { SyndicateHqEntryState state = State; if (((uint)(state - 4) <= 2u || state == SyndicateHqEntryState.Disposed) ? true : false) { return false; } State = (unlocked ? SyndicateHqEntryState.EnterPrompt : SyndicateHqEntryState.LockedPrompt); return true; } public bool BeginEntry(bool returnStateValid) { if (State != SyndicateHqEntryState.EnterPrompt || !returnStateValid) { return false; } State = SyndicateHqEntryState.Entering; return true; } public bool CompleteEntry(bool succeeded) { if (State != SyndicateHqEntryState.Entering) { return false; } State = (succeeded ? SyndicateHqEntryState.Inside : SyndicateHqEntryState.Faulted); return succeeded; } public bool BeginExit() { if (State != SyndicateHqEntryState.Inside) { return false; } State = SyndicateHqEntryState.Exiting; return true; } public bool BeginRecoveryExit() { if (State != SyndicateHqEntryState.Faulted) { return false; } State = SyndicateHqEntryState.Exiting; return true; } public bool CompleteExit(bool succeeded) { if (State != SyndicateHqEntryState.Exiting) { return false; } State = (succeeded ? SyndicateHqEntryState.AwaitingDoor : SyndicateHqEntryState.Faulted); return succeeded; } public void Reset() { if (State != SyndicateHqEntryState.Disposed) { State = SyndicateHqEntryState.AwaitingDoor; } } public void Fault() { if (State != SyndicateHqEntryState.Disposed) { State = SyndicateHqEntryState.Faulted; } } public void Dispose() { State = SyndicateHqEntryState.Disposed; } } public static class SyndicateHqReturnFallbackPlanner { public static SyndicateHqReturnPlan Plan(SyndicateHqExteriorReturnState? state, Guid sessionEpoch, long loadEpoch, string canonicalPlayerId) { if ((object)state == null || state.SessionEpoch != sessionEpoch || state.LoadEpoch != loadEpoch || !string.Equals(state.CanonicalPlayerId, canonicalPlayerId, StringComparison.Ordinal)) { return new SyndicateHqReturnPlan(SyndicateHqReturnPath.NoSafeReturn, SyndicateHqVector3.Zero, "Captured exterior state was stale or unavailable."); } if (IsFinite(state.CapturedPosition)) { return new SyndicateHqReturnPlan(SyndicateHqReturnPath.CapturedExteriorPosition, state.CapturedPosition.Value, "Captured exterior position was valid."); } if (IsFinite(state.AccessPointPosition)) { return new SyndicateHqReturnPlan(SyndicateHqReturnPath.DoorAccessPoint, state.AccessPointPosition.Value, "Validated door access point was used."); } return new SyndicateHqReturnPlan(SyndicateHqReturnPath.NoSafeReturn, SyndicateHqVector3.Zero, "No safe exterior return position was available."); } public static SyndicateHqReturnPlan PlanFreshAccessPoint(SyndicateHqExteriorReturnState? state, Guid sessionEpoch, string canonicalPlayerId, SyndicateHqVector3? accessPointPosition) { if ((object)state == null || state.SessionEpoch != sessionEpoch || !string.Equals(state.CanonicalPlayerId, canonicalPlayerId, StringComparison.Ordinal) || !accessPointPosition.HasValue || !accessPointPosition.Value.IsFinite) { return new SyndicateHqReturnPlan(SyndicateHqReturnPath.NoSafeReturn, SyndicateHqVector3.Zero, "Fresh canonical HQ access point was stale or unavailable."); } return new SyndicateHqReturnPlan(SyndicateHqReturnPath.DoorAccessPoint, accessPointPosition.Value, "Fresh canonical door access point was validated."); } private static bool IsFinite(SyndicateHqVector3? value) { if (value.HasValue) { return value.Value.IsFinite; } return false; } } public static class SyndicateHqIdentity { public static SyndicateHqIdentityCheckResult Validate(SyndicateHqSceneSnapshot? snapshot) { if ((object)snapshot == null) { return SyndicateHqIdentityCheckResult.Reject("Scene snapshot was unavailable."); } int num = snapshot.ShellPaths.Count((string path) => string.Equals(path, "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall", StringComparison.Ordinal)); if (num != 1) { return SyndicateHqIdentityCheckResult.Reject((num == 0) ? "Nightclub shell was missing." : "Nightclub shell was duplicated."); } SyndicateHqDoorFingerprint[] array = snapshot.Doors.Where((SyndicateHqDoorFingerprint door) => string.Equals(door.DoorPath, "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall/Facade (1)/Metal Glass Door (Static)", StringComparison.Ordinal)).ToArray(); if (array.Length != 1) { return SyndicateHqIdentityCheckResult.Reject((array.Length == 0) ? "Validated Nightclub door was missing." : "Validated Nightclub door was duplicated."); } SyndicateHqDoorFingerprint syndicateHqDoorFingerprint = array[0]; if (!syndicateHqDoorFingerprint.Position.IsFinite || !syndicateHqDoorFingerprint.AccessPointPosition.HasValue || !syndicateHqDoorFingerprint.AccessPointPosition.Value.IsFinite) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door position data was non-finite."); } if (!string.Equals(syndicateHqDoorFingerprint.DoorType, "Il2CppScheduleOne.Doors.StaticDoor", StringComparison.Ordinal)) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door runtime type changed."); } if (!string.Equals(syndicateHqDoorFingerprint.InteractablePath, "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall/Facade (1)/Metal Glass Door (Static)/IntObj", StringComparison.Ordinal) || !string.Equals(syndicateHqDoorFingerprint.InteractableType, "Il2CppScheduleOne.Interaction.InteractableObject", StringComparison.Ordinal)) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door interaction target changed."); } if (!string.Equals(syndicateHqDoorFingerprint.BuildingPath, "Map/Hyland Point/Region_Northtown/Nightclub", StringComparison.Ordinal) || !string.Equals(syndicateHqDoorFingerprint.BuildingType, "Il2CppScheduleOne.Map.NPCEnterableBuilding", StringComparison.Ordinal) || !string.Equals(syndicateHqDoorFingerprint.BuildingGuid, "6121cc6b-ccc6-4c9e-b4cb-2d2db634f671", StringComparison.Ordinal)) { return SyndicateHqIdentityCheckResult.Reject("Nightclub building identity changed."); } if (!string.Equals(syndicateHqDoorFingerprint.AccessPointPath, "Map/Hyland Point/Region_Northtown/Nightclub/desert town hall/Facade (1)/Metal Glass Door (Static)/AccessPoint", StringComparison.Ordinal)) { return SyndicateHqIdentityCheckResult.Reject("Nightclub access point changed."); } if (syndicateHqDoorFingerprint.DoorIndex != 0) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door index changed."); } if (!syndicateHqDoorFingerprint.HasIntObj || !syndicateHqDoorFingerprint.HasBuilding || !syndicateHqDoorFingerprint.HasAccessPoint || !syndicateHqDoorFingerprint.HasCanKnock || !syndicateHqDoorFingerprint.HasUsable) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door fingerprint fields were incomplete."); } SyndicateHqVector3 other = new SyndicateHqVector3(-43.746002f, -4.000184f, 156.47406f); SyndicateHqVector3 other2 = new SyndicateHqVector3(1f, 0f, 0f); if (syndicateHqDoorFingerprint.Position.DistanceTo(other) > 0.1f) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door position moved beyond tolerance."); } if (!syndicateHqDoorFingerprint.Forward.IsNonZero) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door forward vector was zero or non-finite."); } if (syndicateHqDoorFingerprint.Forward.NormalizedDot(other2) < 0.999f) { return SyndicateHqIdentityCheckResult.Reject("Nightclub door orientation changed."); } return SyndicateHqIdentityCheckResult.Accept(syndicateHqDoorFingerprint); } } public enum SyndicateHqMaterialRole { Floor, Brick, Ceiling, Wood, Metal, Accent } public sealed record SyndicateHqMarkerDefinition(string Name, SyndicateHqVector3 Position, bool IsInert); public sealed record SyndicateHqPrimitiveDefinition(string Name, SyndicateHqVector3 Position, SyndicateHqVector3 Scale, SyndicateHqMaterialRole MaterialRole, bool HasCollider, bool IsVisible, bool IsRequiredCollider = false); public enum SyndicateHqNativePropAnchor { Floor, Center } public sealed record SyndicateHqNativePropDefinition(string Name, string SourceHierarchyPath, SyndicateHqVector3 Position, float YawDegrees, float UniformScale, SyndicateHqNativePropAnchor Anchor, bool KeepColliders, bool IsRequired = true, bool IncludeInactiveChildren = false, float PitchDegrees = 0f, float RollDegrees = 0f); public readonly record struct SyndicateHqBoundsSnapshot(SyndicateHqVector3 Center, SyndicateHqVector3 Minimum); public static class SyndicateHqNativePropPlacement { public static SyndicateHqVector3 AlignmentOffset(SyndicateHqVector3 target, SyndicateHqBoundsSnapshot bounds, SyndicateHqNativePropAnchor anchor) { SyndicateHqVector3 syndicateHqVector = ((anchor == SyndicateHqNativePropAnchor.Floor) ? new SyndicateHqVector3(bounds.Center.X, bounds.Minimum.Y, bounds.Center.Z) : bounds.Center); return new SyndicateHqVector3(target.X - syndicateHqVector.X, target.Y - syndicateHqVector.Y, target.Z - syndicateHqVector.Z); } } public sealed record SyndicateHqLightDefinition(string Name, SyndicateHqVector3 Position, SyndicateHqColor Color, float Intensity, float Range); public readonly record struct SyndicateHqColor(float R, float G, float B); public static class SyndicateHqPromptLayout { public const float Width = 560f; public const float Height = 156f; public const float ActionHeight = 48f; public const int BodyFontSize = 22; public const int ActionFontSize = 20; } public static class SyndicateHqInteriorDefinition { public const float Width = 14f; public const float Depth = 10f; public const float Height = 4.5f; public const float PocketOffset = 1000f; public static IReadOnlyList Markers { get; } = new SyndicateHqMarkerDefinition[3] { new SyndicateHqMarkerDefinition("HQ_ENTRY", new SyndicateHqVector3(0f, 1f, -3f), IsInert: false), new SyndicateHqMarkerDefinition("HQ_EXIT", new SyndicateHqVector3(0f, 1f, -4.5f), IsInert: false), new SyndicateHqMarkerDefinition("HQ_STORAGE", new SyndicateHqVector3(5.4f, 1f, 1.4f), IsInert: true) }; public static IReadOnlyList Primitives { get; } = new SyndicateHqPrimitiveDefinition[10] { new SyndicateHqPrimitiveDefinition("Floor", new SyndicateHqVector3(0f, -0.2f, 0f), new SyndicateHqVector3(14f, 0.4f, 10f), SyndicateHqMaterialRole.Floor, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("SafetyFloor", new SyndicateHqVector3(0f, -1.7f, 0f), new SyndicateHqVector3(16f, 3f, 12f), SyndicateHqMaterialRole.Floor, HasCollider: true, IsVisible: false, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Ceiling", new SyndicateHqVector3(0f, 4.5f, 0f), new SyndicateHqVector3(14f, 0.2f, 10f), SyndicateHqMaterialRole.Ceiling, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Wall_North", new SyndicateHqVector3(0f, 2.25f, 5f), new SyndicateHqVector3(14f, 4.5f, 0.25f), SyndicateHqMaterialRole.Brick, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Wall_West", new SyndicateHqVector3(-7f, 2.25f, 0f), new SyndicateHqVector3(0.25f, 4.5f, 10f), SyndicateHqMaterialRole.Brick, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Wall_East", new SyndicateHqVector3(7f, 2.25f, 0f), new SyndicateHqVector3(0.25f, 4.5f, 10f), SyndicateHqMaterialRole.Brick, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Wall_South_Left", new SyndicateHqVector3(-4f, 2.25f, -5f), new SyndicateHqVector3(6f, 4.5f, 0.25f), SyndicateHqMaterialRole.Brick, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Wall_South_Right", new SyndicateHqVector3(4f, 2.25f, -5f), new SyndicateHqVector3(6f, 4.5f, 0.25f), SyndicateHqMaterialRole.Brick, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("Door_Header", new SyndicateHqVector3(0f, 3.5f, -5f), new SyndicateHqVector3(2f, 2f, 0.25f), SyndicateHqMaterialRole.Brick, HasCollider: true, IsVisible: true, IsRequiredCollider: true), new SyndicateHqPrimitiveDefinition("DoorBarrier", new SyndicateHqVector3(0f, 1.25f, -4.86f), new SyndicateHqVector3(1.8f, 2.5f, 0.18f), SyndicateHqMaterialRole.Metal, HasCollider: true, IsVisible: false, IsRequiredCollider: true) }; public static IReadOnlyList NativeProps { get; } = new SyndicateHqNativePropDefinition[24] { new SyndicateHqNativePropDefinition("EntranceDoorNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/dockswarehouse/Walls/DoorFrameWall/Industrial Metal Door/Container/IndustrialMetalDoor", new SyndicateHqVector3(0f, 0f, -4.86f), 90f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: false, IsRequired: true, IncludeInactiveChildren: true), new SyndicateHqNativePropDefinition("PlanningTableNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Office_Table", new SyndicateHqVector3(-3.6f, 0f, 0.45f), 90f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("BriefingChairNativeA", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Outdoor chair", new SyndicateHqVector3(-4.9f, 0f, 0.45f), 90f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("BriefingChairNativeB", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Outdoor chair", new SyndicateHqVector3(-2.3f, 0f, 0.45f), 270f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("BriefingChairNativeC", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Outdoor chair", new SyndicateHqVector3(-3.6f, 0f, -0.75f), 0f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("BriefingChairNativeD", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Outdoor chair", new SyndicateHqVector3(-3.6f, 0f, 1.65f), 180f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("BossDeskNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/InteriorDisable/Mezzanine/Ornate Desk/ornate desk", new SyndicateHqVector3(-1.4f, 0f, 3.15f), 180f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true, IsRequired: true, IncludeInactiveChildren: false, -90f), new SyndicateHqNativePropDefinition("BossArmchairNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Armchair", new SyndicateHqVector3(-1.4f, 0f, 4.15f), 180f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("BossWhiskyNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/InteriorDisable/Mezzanine/Ornate Desk/Whisky Bottle", new SyndicateHqVector3(-2f, 1.05f, 3.15f), 20f, 1f, SyndicateHqNativePropAnchor.Center, KeepColliders: false), new SyndicateHqNativePropDefinition("EnforcerDeskNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Frontdesk", new SyndicateHqVector3(-4.1f, 0f, 3.15f), 180f, 0.65f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("EnforcerArmchairNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Armchair (1)", new SyndicateHqVector3(-4.1f, 0f, 4.05f), 180f, 0.9f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("FilingCabinetNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/InteriorDisable/Mezzanine/docks mezzanine/Filing Cabinet", new SyndicateHqVector3(-6f, 0f, -1.25f), 90f, 1f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("LoungeSofaNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Double Sofa", new SyndicateHqVector3(-5.7f, 0f, -2.8f), 90f, 0.9f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("LoungeCoffeeTableNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Coffee Table", new SyndicateHqVector3(-4.45f, 0f, -2.65f), 0f, 0.8f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("LoungeArmchairNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Armchair", new SyndicateHqVector3(-3.35f, 0f, -3.35f), 0f, 0.85f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("CargoCrateNativeA", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Wood Crate Prop", new SyndicateHqVector3(0.75f, 0f, -3.25f), 15f, 0.9f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("CargoCrateNativeB", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Interior/Wood Crate Prop", new SyndicateHqVector3(1.5f, 0f, -2.85f), 345f, 0.7f, SyndicateHqNativePropAnchor.Floor, KeepColliders: true), new SyndicateHqNativePropDefinition("WhiteboardNative", "Map/Hyland Point/Region_Docks/Dark Market Area/Docks Warehouse/Whiteboard/whiteboard", new SyndicateHqVector3(-6.82f, 2.35f, 0.6f), 90f, 1f, SyndicateHqNativePropAnchor.Center, KeepColliders: false), new SyndicateHqNativePropDefinition("OperationsRadioNative", "Map/Hyland Point/Region_Docks/Clothing store with interior/Interior/SM_Item_Radio_01", new SyndicateHqVector3(-4f, 1.05f, 3.15f), 90f, 0.8f, SyndicateHqNativePropAnchor.Center, KeepColliders: false, IsRequired: false), new SyndicateHqNativePropDefinition("SafeNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Safe", new SyndicateHqVector3(-6.05f, 0f, 3.65f), 90f, 0.9f, SyndicateHqNativePropAnchor.Floor, KeepColliders: false, IsRequired: false), new SyndicateHqNativePropDefinition("WallCabinetNative", "Map/Hyland Point/Region_Northtown/Motel/Office_Complete/Office Props/Office_Wallcabinet", new SyndicateHqVector3(-6.82f, 2.25f, 2.15f), 90f, 0.85f, SyndicateHqNativePropAnchor.Center, KeepColliders: false, IsRequired: false), new SyndicateHqNativePropDefinition("WallClockNative", "Map/Hyland Point/Region_Docks/Clothing store with interior/Interior/Wall Clock/Wall clock/Clock", new SyndicateHqVector3(0f, 3.2f, 4.78f), 90f, 0.9f, SyndicateHqNativePropAnchor.Center, KeepColliders: false, IsRequired: false, IncludeInactiveChildren: false, -90f), new SyndicateHqNativePropDefinition("WallLanternNativeLeft", "Map/Hyland Point/Region_Downtown/TownCenter/Bank/WallLantern/walllantern", new SyndicateHqVector3(-4.8f, 2.75f, 4.78f), 180f, 1f, SyndicateHqNativePropAnchor.Center, KeepColliders: false, IsRequired: false), new SyndicateHqNativePropDefinition("WallLanternNativeRight", "Map/Hyland Point/Region_Downtown/TownCenter/Bank/WallLantern/walllantern", new SyndicateHqVector3(4.8f, 2.75f, 4.78f), 180f, 1f, SyndicateHqNativePropAnchor.Center, KeepColliders: false, IsRequired: false) }; public static IReadOnlyList Lights { get; } = new SyndicateHqLightDefinition[4] { new SyndicateHqLightDefinition("EntryLight", new SyndicateHqVector3(0f, 3.65f, -3.3f), new SyndicateHqColor(1f, 0.58f, 0.27f), 1.65f, 7f), new SyndicateHqLightDefinition("PlanningLight", new SyndicateHqVector3(-3.6f, 3.75f, 0.45f), new SyndicateHqColor(1f, 0.68f, 0.38f), 2.15f, 8f), new SyndicateHqLightDefinition("BossLight", new SyndicateHqVector3(0f, 3.55f, 3.15f), new SyndicateHqColor(1f, 0.5f, 0.2f), 1.55f, 6f), new SyndicateHqLightDefinition("StorageLight", new SyndicateHqVector3(5.2f, 3.25f, 0.5f), new SyndicateHqColor(0.72f, 0.8f, 1f), 1.15f, 5f) }; public static IReadOnlyList RequiredColliderNames { get; } = (from item in Primitives where item.IsRequiredCollider select item.Name).ToArray(); public static SyndicateHqVector3 GetPocketRoot(SyndicateHqVector3 exteriorAnchor) { return exteriorAnchor + new SyndicateHqVector3(1000f, 0f, 1000f); } public static bool IsValid(out string reason) { string[] array = Markers.Select((SyndicateHqMarkerDefinition marker) => marker.Name).ToArray(); if (array.Distinct(StringComparer.Ordinal).Count() != array.Length) { reason = "HQ marker names were duplicated."; return false; } if (Markers.Count((SyndicateHqMarkerDefinition marker) => marker.Name == "HQ_ENTRY") != 1 || Markers.Count((SyndicateHqMarkerDefinition marker) => marker.Name == "HQ_EXIT") != 1) { reason = "HQ entry/exit markers were incomplete."; return false; } if (!Markers.Single((SyndicateHqMarkerDefinition marker) => marker.Name == "HQ_STORAGE").IsInert) { reason = "HQ storage marker was not inert."; return false; } if (Markers.Any((SyndicateHqMarkerDefinition marker) => !marker.Position.IsFinite)) { reason = "HQ marker position was not finite."; return false; } string[] array2 = Primitives.Select((SyndicateHqPrimitiveDefinition item) => item.Name).ToArray(); if (array2.Distinct(StringComparer.Ordinal).Count() != array2.Length) { reason = "HQ primitive names were duplicated."; return false; } if (Primitives.Any((SyndicateHqPrimitiveDefinition item) => !item.Position.IsFinite || !item.Scale.IsFinite || item.Scale.X <= 0f || item.Scale.Y <= 0f || item.Scale.Z <= 0f)) { reason = "HQ primitive geometry was invalid."; return false; } if (RequiredColliderNames.Count == 0 || RequiredColliderNames.Any((string name) => !Primitives.Any((SyndicateHqPrimitiveDefinition item) => item.Name == name && item.HasCollider))) { reason = "HQ required collider shell was incomplete."; return false; } string[] array3 = NativeProps.Select((SyndicateHqNativePropDefinition item) => item.Name).ToArray(); if (array3.Distinct(StringComparer.Ordinal).Count() != array3.Length) { reason = "HQ native prop names were duplicated."; return false; } if (NativeProps.Count((SyndicateHqNativePropDefinition item) => item.IsRequired) < 10 || NativeProps.Any((SyndicateHqNativePropDefinition item) => string.IsNullOrWhiteSpace(item.Name) || string.IsNullOrWhiteSpace(item.SourceHierarchyPath) || !item.SourceHierarchyPath.StartsWith("Map/Hyland Point/", StringComparison.Ordinal) || !item.Position.IsFinite || !float.IsFinite(item.PitchDegrees) || !float.IsFinite(item.YawDegrees) || !float.IsFinite(item.RollDegrees) || !float.IsFinite(item.UniformScale) || item.UniformScale <= 0f)) { reason = "HQ native prop catalog was invalid."; return false; } string[] array4 = Lights.Select((SyndicateHqLightDefinition light) => light.Name).ToArray(); if (array4.Distinct(StringComparer.Ordinal).Count() != array4.Length || Lights.Any((SyndicateHqLightDefinition light) => !light.Position.IsFinite || light.Intensity <= 0f || light.Range <= 0f)) { reason = "HQ lighting definition was invalid."; return false; } reason = "Deterministic visible HQ shell and inert native prop catalog are valid."; return true; } } public sealed record SyndicateHqInteriorOwnershipSnapshot(string? OwnedRootToken, IReadOnlyList SceneRootTokens, IReadOnlySet MarkerNames, IReadOnlySet ColliderGeometryNames, bool DestroyPending); public static class SyndicateHqInteriorOwnership { public static bool IsRetainedNativeRoot(int retainedInstanceId, IReadOnlyList sceneRootInstanceIds, out string reason) { if (sceneRootInstanceIds.Count != 1) { reason = $"the scene contained {sceneRootInstanceIds.Count} HQ roots"; return false; } if (sceneRootInstanceIds[0] != retainedInstanceId) { reason = "the scene root native instance identity did not match the retained owned root"; return false; } reason = "the scene root native instance identity matched the retained owned root"; return true; } public static bool CanReuse(SyndicateHqInteriorOwnershipSnapshot snapshot, out string reason) { if (snapshot.DestroyPending) { reason = "Owned HQ root is pending deferred destruction."; return false; } if (string.IsNullOrWhiteSpace(snapshot.OwnedRootToken)) { reason = "No exact runtime-created HQ root reference was retained."; return false; } if (snapshot.SceneRootTokens.Count != 1 || !string.Equals(snapshot.SceneRootTokens[0], snapshot.OwnedRootToken, StringComparison.Ordinal)) { reason = "A foreign or duplicate same-name HQ root was present."; return false; } if (!SyndicateHqInteriorDefinition.Markers.All((SyndicateHqMarkerDefinition marker) => snapshot.MarkerNames.Contains(marker.Name))) { reason = "Owned HQ root markers were incomplete."; return false; } if (!SyndicateHqInteriorDefinition.RequiredColliderNames.All(snapshot.ColliderGeometryNames.Contains)) { reason = "Owned HQ geometry/collider shell was incomplete."; return false; } reason = "Exact runtime-created HQ root is reusable."; return true; } } public enum SyndicateHqStorageStatus { NotPrepared, Ready, NotCanonicalHost, SaveBindingChanged, GridUnavailable, IdentityConflict, SpawnUnavailable, ValidationFailed, Disposed } public sealed record SyndicateHqStorageDefinition(string Name, Guid Guid, string ItemId, int RequiredSlotCount, SyndicateHqVector3 LocalPosition, float YawDegrees, int GridOriginX, int GridOriginY); public readonly record struct SyndicateHqStorageGridCoordinate(int X, int Y); public enum SyndicateHqStorageGridShape { Invalid, LegacyTwoCloset, Current } public static class SyndicateHqStorageGridContract { public const int LegacyWidth = 12; public const int LegacyDepth = 14; public const int Width = 16; public const int Depth = 32; public static SyndicateHqStorageGridShape Classify(IEnumerable coordinates) { ArgumentNullException.ThrowIfNull(coordinates, "coordinates"); SyndicateHqStorageGridCoordinate[] array = coordinates.ToArray(); if (IsCompleteRectangle((IReadOnlyCollection)(object)array, 16, 32)) { return SyndicateHqStorageGridShape.Current; } if (IsCompleteRectangle((IReadOnlyCollection)(object)array, 12, 14) && array.Contains(new SyndicateHqStorageGridCoordinate(4, 3)) && array.Contains(new SyndicateHqStorageGridCoordinate(4, 9))) { return SyndicateHqStorageGridShape.LegacyTwoCloset; } return SyndicateHqStorageGridShape.Invalid; } private static bool IsCompleteRectangle(IReadOnlyCollection coordinates, int width, int depth) { if (coordinates.Count == width * depth && coordinates.Distinct().Count() == coordinates.Count) { return coordinates.All((SyndicateHqStorageGridCoordinate item) => item.X >= 0 && item.X < width && item.Y >= 0 && item.Y < depth); } return false; } } public sealed record SyndicateHqStorageContext(bool IsCanonicalHost, string CanonicalPlayerId, string BoundSaveFolder, string ActiveSaveFolder); public sealed record SyndicateHqStorageResult(SyndicateHqStorageStatus Status, string Reason) { public bool IsReady => Status == SyndicateHqStorageStatus.Ready; } public static class SyndicateHqStorageContract { public const string ItemId = "hugestoragecloset"; public const int SlotCount = 20; public static IReadOnlyList Definitions { get; } = new SyndicateHqStorageDefinition[9] { new SyndicateHqStorageDefinition("HQ_PlayerLocker_A", Guid.Parse("8ec9d63b-f0f7-4af9-86fb-f1c73c7af481"), "hugestoragecloset", 20, new SyndicateHqVector3(5.2f, 0f, -1.8f), 270f, 4, 3), new SyndicateHqStorageDefinition("HQ_PlayerLocker_B", Guid.Parse("dcf4ca2a-4d27-47c4-a10a-2819ea298fe3"), "hugestoragecloset", 20, new SyndicateHqVector3(5.2f, 0f, 1.8f), 270f, 4, 9), new SyndicateHqStorageDefinition("HQ_PlayerLocker_C", Guid.Parse("7c4553ca-837a-4daa-8c0e-8c5021ab8b3c"), "hugestoragecloset", 20, new SyndicateHqVector3(5.2f, 0f, -3.6f), 270f, 4, 15), new SyndicateHqStorageDefinition("HQ_PlayerLocker_D", Guid.Parse("6a47a1fa-cc74-4eb6-8cd1-73c3ecf5a28f"), "hugestoragecloset", 20, new SyndicateHqVector3(5.2f, 0f, 0f), 270f, 4, 21), new SyndicateHqStorageDefinition("HQ_PlayerLocker_E", Guid.Parse("77853faf-16c9-483a-a4b6-0c12cb85ad5b"), "hugestoragecloset", 20, new SyndicateHqVector3(5.2f, 0f, 3.6f), 270f, 4, 27), new SyndicateHqStorageDefinition("HQ_PlayerLocker_F", Guid.Parse("6d19eda6-72fe-4e3c-ae68-8bd7f205713b"), "hugestoragecloset", 20, new SyndicateHqVector3(2.4f, 0f, -3.6f), 90f, 10, 3), new SyndicateHqStorageDefinition("HQ_PlayerLocker_G", Guid.Parse("a92fc0f8-19fa-445c-9ef1-5af74904094d"), "hugestoragecloset", 20, new SyndicateHqVector3(2.4f, 0f, -1.8f), 90f, 10, 9), new SyndicateHqStorageDefinition("HQ_PlayerLocker_I", Guid.Parse("bb63e787-4c46-4627-8f20-e2429f13a574"), "hugestoragecloset", 20, new SyndicateHqVector3(2.4f, 0f, 1.8f), 90f, 10, 21), new SyndicateHqStorageDefinition("HQ_PlayerLocker_J", Guid.Parse("11164773-6a3d-4b11-862a-9ac447626ec1"), "hugestoragecloset", 20, new SyndicateHqVector3(2.4f, 0f, 3.6f), 90f, 10, 27) }; public static bool IsValid(out string reason) { if (Definitions.Count != 9) { reason = "HQ storage must define exactly nine lockers."; return false; } if (Definitions.Select((SyndicateHqStorageDefinition definition) => definition.Name).Distinct(StringComparer.Ordinal).Count() != Definitions.Count || Definitions.Select((SyndicateHqStorageDefinition definition) => definition.Guid).Distinct().Count() != Definitions.Count) { reason = "HQ storage names and GUIDs must be distinct."; return false; } if (Definitions.Any((SyndicateHqStorageDefinition definition) => string.IsNullOrWhiteSpace(definition.Name) || definition.Guid == Guid.Empty || !string.Equals(definition.ItemId, "hugestoragecloset", StringComparison.Ordinal) || definition.RequiredSlotCount != 20 || !definition.LocalPosition.IsFinite || !float.IsFinite(definition.YawDegrees) || definition.GridOriginX < 0 || definition.GridOriginX >= 16 || definition.GridOriginY < 0 || definition.GridOriginY >= 32)) { reason = "HQ storage definition values were invalid."; return false; } if (Definitions.Select((SyndicateHqStorageDefinition definition) => new SyndicateHqStorageGridCoordinate(definition.GridOriginX, definition.GridOriginY)).Distinct().Count() != Definitions.Count) { reason = "HQ storage native Grid origins must be distinct."; return false; } reason = "Nine distinct native 20-slot Huge Storage Closets are defined (180 total slots)."; return true; } } public static class SyndicateHqStorageCullingGuard { private static readonly object Sync = new object(); private static readonly HashSet VisibleClosets = new HashSet(); public static void SetVisible(Guid guid, bool visible) { if (!SyndicateHqStorageContract.Definitions.Any((SyndicateHqStorageDefinition definition) => definition.Guid == guid)) { return; } lock (Sync) { if (visible) { VisibleClosets.Add(guid); } else { VisibleClosets.Remove(guid); } } } public static bool Filter(Guid guid, bool requestedCulled) { if (!requestedCulled) { return false; } lock (Sync) { return !VisibleClosets.Contains(guid); } } public static void ApplyVisibility(bool enabled, Action setGuardVisible, Action requestUncull, Action setActive) { setGuardVisible(enabled); if (enabled) { requestUncull(); } setActive(enabled); } public static void Reset() { lock (Sync) { VisibleClosets.Clear(); } } } public static class SyndicateHqStorageAdmission { public static SyndicateHqStorageResult Evaluate(SyndicateHqStorageContext context) { if (!context.IsCanonicalHost || !LocalPressureHostLifecyclePolicies.IsCanonicalPlayerIdentity(context.CanonicalPlayerId)) { return new SyndicateHqStorageResult(SyndicateHqStorageStatus.NotCanonicalHost, "Canonical single-player host authority was unavailable."); } if (!TryReadSaveIdentity(context.BoundSaveFolder, out string identity) || !TryReadSaveIdentity(context.ActiveSaveFolder, out string identity2) || !string.Equals(identity, context.CanonicalPlayerId, StringComparison.Ordinal) || !string.Equals(identity2, context.CanonicalPlayerId, StringComparison.Ordinal) || !TryNormalizePath(context.BoundSaveFolder, out string normalized) || !TryNormalizePath(context.ActiveSaveFolder, out string normalized2) || !string.Equals(normalized, normalized2, StringComparison.OrdinalIgnoreCase)) { return new SyndicateHqStorageResult(SyndicateHqStorageStatus.SaveBindingChanged, "The active save binding did not match the canonical bound save."); } return new SyndicateHqStorageResult(SyndicateHqStorageStatus.Ready, "Canonical host and save binding admit native HQ storage preparation."); } private static bool TryReadSaveIdentity(string path, out string identity) { identity = string.Empty; if (!LocalPressureHostLifecyclePolicies.TryGetCanonicalHostIdentity(path, out string identity2) || string.IsNullOrWhiteSpace(identity2)) { return false; } identity = identity2; return true; } private static bool TryNormalizePath(string path, out string normalized) { normalized = string.Empty; try { normalized = Path.TrimEndingDirectorySeparator(Path.GetFullPath(path)); return true; } catch { return false; } } } public sealed record SyndicateHqUnlockDecision(bool IsUnlocked, string Reason) { public static SyndicateHqUnlockDecision Locked(string reason) { return new SyndicateHqUnlockDecision(IsUnlocked: false, reason); } public static SyndicateHqUnlockDecision Unlocked() { return new SyndicateHqUnlockDecision(IsUnlocked: true, "OC-10 Release 1 recognition authorizes Syndicate HQ entry."); } public static SyndicateHqUnlockDecision UnlockedByWrongAddressSatisfied() { return new SyndicateHqUnlockDecision(IsUnlocked: true, "Wrong Address is satisfied, so Room With No Name's HQ closets are authorized ahead of full Release 1 recognition."); } } public static class SyndicateHqUnlockAdmission { public static SyndicateHqUnlockDecision Evaluate(Release1StoryHostContextReadStatus contextStatus, Release1StoryHostContextSnapshot? context, Release1StoryState? story) { if (contextStatus != Release1StoryHostContextReadStatus.Ready || !context.HasValue) { return SyndicateHqUnlockDecision.Locked("Canonical host authority was not ready."); } Release1StoryHostContextSnapshot currentContext = context.Value; if (!LocalPressureHostLifecyclePolicies.IsCanonicalPlayerIdentity(currentContext.PlayerId) || !LocalPressureHostLifecyclePolicies.TryGetCanonicalHostIdentity(currentContext.ActiveSaveFolder, out string identity) || !string.Equals(identity, currentContext.PlayerId, StringComparison.Ordinal)) { return SyndicateHqUnlockDecision.Locked("Canonical save-account identity did not match the host identity."); } if ((object)story == null || !string.Equals(story.PlayerId, currentContext.PlayerId, StringComparison.Ordinal)) { return SyndicateHqUnlockDecision.Locked("OC-10 Release 1 recognition has not been authorized for this host."); } if (story.Release1Recognized && story.RecognitionLogicalCorrelationIds.Any((string id) => Release1LogicalCorrelation.TryParse(id, out Release1LogicalCorrelation correlation) && correlation.PlayerId == currentContext.PlayerId && correlation.MissionKey == "release1.intro" && correlation.Attempt == 0 && correlation.TransitionKind == Release1TransitionKind.Release1Recognized)) { return SyndicateHqUnlockDecision.Unlocked(); } if (!story.Missions.Any((Release1MissionRecord mission) => mission.MissionKey == "release1.wrong-address" && mission.State == Release1MissionState.Satisfied)) { return SyndicateHqUnlockDecision.Locked("OC-10 recognition correlation was missing or non-canonical, and Wrong Address is not yet satisfied."); } return SyndicateHqUnlockDecision.UnlockedByWrongAddressSatisfied(); } } }