Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of SkipDropshipCompany v0.2.5
com.aoirint.SkipDropshipCompany.dll
Decompiled a week agousing System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using Newtonsoft.Json; using SkipDropshipCompany.Core.Handlers; using SkipDropshipCompany.Core.Ports; using SkipDropshipCompany.Core.State; using SkipDropshipCompany.Core.UseCases; using SkipDropshipCompany.Core.Validation; using SkipDropshipCompany.Interop; using SkipDropshipCompany.Interop.Game; using SkipDropshipCompany.Interop.Game.Adapters; using SkipDropshipCompany.Interop.Game.Patches; using Unity.Mathematics; using Unity.Netcode; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("com.aoirint.SkipDropshipCompany")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyFileVersion("0.2.5.0")] [assembly: AssemblyInformationalVersion("0.2.5+1ae3967a6ac254d67bc4a7301324beb2bda02882")] [assembly: AssemblyProduct("SkipDropshipCompany")] [assembly: AssemblyTitle("com.aoirint.SkipDropshipCompany")] [assembly: AssemblyVersion("0.2.5.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.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] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace SkipDropshipCompany { internal sealed class PluginController { private readonly RoundCallbackHandler roundCallbackHandler; private readonly TerminalSyncGroupCreditsHandler terminalSyncGroupCreditsHandler; private PluginController(RoundCallbackHandler roundCallbackHandler, TerminalSyncGroupCreditsHandler terminalSyncGroupCreditsHandler) { this.roundCallbackHandler = roundCallbackHandler; this.terminalSyncGroupCreditsHandler = terminalSyncGroupCreditsHandler; } public static PluginController Create(IPluginConfig config, IPluginLogger logger, IValidationLogger validationLogger) { IGameInterop gameInterop = new GameInterop(logger); LandingHistoryStore landingHistoryStore = new LandingHistoryStore(logger); PreparedInstantPurchaseStore preparedInstantPurchaseStore = new PreparedInstantPurchaseStore(); InstantPurchaseEligibilityUseCase eligibilityUseCase = new InstantPurchaseEligibilityUseCase(config, gameInterop, landingHistoryStore, logger, validationLogger); PrepareInstantPurchaseUseCase prepareInstantPurchaseUseCase = new PrepareInstantPurchaseUseCase(gameInterop, eligibilityUseCase, preparedInstantPurchaseStore, logger, validationLogger); SpawnPreparedInstantPurchasedItemsUseCase spawnPreparedInstantPurchasedItemsUseCase = new SpawnPreparedInstantPurchasedItemsUseCase(gameInterop, preparedInstantPurchaseStore, logger, validationLogger); validationLogger.Record(ValidationLogRecord.ControllerCreated()); RecordLandingUseCase recordLandingUseCase = new RecordLandingUseCase(gameInterop, landingHistoryStore, logger, validationLogger); ClearLandingHistoryUseCase clearLandingHistoryUseCase = new ClearLandingHistoryUseCase(gameInterop, landingHistoryStore, logger, validationLogger); return new PluginController(new RoundCallbackHandler(gameInterop, recordLandingUseCase, clearLandingHistoryUseCase), new TerminalSyncGroupCreditsHandler(gameInterop, prepareInstantPurchaseUseCase, spawnPreparedInstantPurchasedItemsUseCase, logger, validationLogger)); } public void HandleStartGame() { roundCallbackHandler.HandleStartGame(); } public void HandleResetShip() { roundCallbackHandler.HandleResetShip(); } public PrepareInstantPurchaseResult? HandleTerminalSyncGroupCreditsClientRpcPrefix() { return terminalSyncGroupCreditsHandler.HandlePrefix(); } public void HandleTerminalSyncGroupCreditsClientRpcPostfix() { terminalSyncGroupCreditsHandler.HandlePostfix(); } } [BepInPlugin("com.aoirint.SkipDropshipCompany", "SkipDropshipCompany", "0.2.5")] [BepInProcess("Lethal Company.exe")] public class SkipDropshipCompany : BaseUnityPlugin { private static PluginController? controller; internal static PluginController Controller => controller; private void Awake() { BepInExPluginLogger bepInExPluginLogger = new BepInExPluginLogger(((BaseUnityPlugin)this).Logger); BepInExPluginConfig bepInExPluginConfig = BepInExPluginConfig.Bind(((BaseUnityPlugin)this).Config); IValidationLogger validationLogger; if (!bepInExPluginConfig.ValidationLogging) { IValidationLogger instance = DisabledValidationLogger.Instance; validationLogger = instance; } else { IValidationLogger instance = new BepInExValidationLogger(bepInExPluginLogger, DateTime.UtcNow); validationLogger = instance; } IValidationLogger validationLogger2 = validationLogger; validationLogger2.Record(ValidationLogRecord.PluginLoaded("0.2.5", bepInExPluginConfig.ValidationLogging, bepInExPluginConfig.Enabled, bepInExPluginConfig.RequireReroutingOnFirstDay)); controller = PluginController.Create(bepInExPluginConfig, bepInExPluginLogger, validationLogger2); HarmonyCallbackGuard.Configure(new HarmonyCallbackDiagnosticReporter(bepInExPluginLogger, validationLogger2)); HarmonyPatchInstaller.Install(); bepInExPluginLogger.LogInfo("Plugin SkipDropshipCompany v0.2.5 is loaded!"); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "com.aoirint.SkipDropshipCompany"; public const string PLUGIN_NAME = "SkipDropshipCompany"; public const string PLUGIN_VERSION = "0.2.5"; } } namespace SkipDropshipCompany.Interop { internal sealed class BepInExPluginConfig : IPluginConfig { private readonly ConfigEntry<bool> enabledConfig; private readonly ConfigEntry<bool> requireReroutingOnFirstDayConfig; private readonly ConfigEntry<bool> validationLoggingConfig; public bool Enabled => enabledConfig.Value; public bool RequireReroutingOnFirstDay => requireReroutingOnFirstDayConfig.Value; public bool ValidationLogging => validationLoggingConfig.Value; private BepInExPluginConfig(ConfigEntry<bool> enabledConfig, ConfigEntry<bool> requireReroutingOnFirstDayConfig, ConfigEntry<bool> validationLoggingConfig) { this.enabledConfig = enabledConfig; this.requireReroutingOnFirstDayConfig = requireReroutingOnFirstDayConfig; this.validationLoggingConfig = validationLoggingConfig; } public static BepInExPluginConfig Bind(ConfigFile config) { ConfigEntry<bool> obj = config.Bind<bool>("General", "Enabled", true, "Set to false to disable this mod."); ConfigEntry<bool> val = config.Bind<bool>("General", "RequireReroutingOnFirstDay", false, "If true, rerouting to the company will be required to skip the dropship on the first day."); ConfigEntry<bool> val2 = config.Bind<bool>("Debug", "ValidationLogging", false, "Enable structured validation logs for release validation and troubleshooting."); return new BepInExPluginConfig(obj, val, val2); } } internal sealed class BepInExPluginLogger : IPluginLogger { private readonly ManualLogSource logger; public BepInExPluginLogger(ManualLogSource logger) { this.logger = logger; } public void LogDebug(string message) { logger.LogDebug((object)message); } public void LogInfo(string message) { logger.LogInfo((object)message); } public void LogError(string message) { logger.LogError((object)message); } } internal sealed class BepInExValidationLogger : IValidationLogger { private const int SchemaVersion = 1; private const string Prefix = "[SDC_VALIDATION] "; private readonly IPluginLogger logger; private readonly string runId; private int sequence; public BepInExValidationLogger(IPluginLogger logger, DateTime startupTimeUtc) { this.logger = logger; runId = CreateRunId(startupTimeUtc); } public void Record(ValidationLogRecord record) { Dictionary<string, object> dictionary = new Dictionary<string, object> { ["schema"] = 1, ["ts"] = FormatTimestamp(DateTime.UtcNow), ["run"] = runId, ["seq"] = ++sequence, ["event"] = record.EventName }; if (record.Fields != null) { foreach (KeyValuePair<string, object> field in record.Fields) { dictionary[field.Key] = field.Value; } } logger.LogInfo("[SDC_VALIDATION] " + JsonConvert.SerializeObject((object)dictionary, (Formatting)0)); } private static string CreateRunId(DateTime startupTimeUtc) { string text = startupTimeUtc.ToString("yyyyMMdd'T'HHmmss'Z'", CultureInfo.InvariantCulture); string text2 = Guid.NewGuid().ToString("N", CultureInfo.InvariantCulture).Substring(0, 6); return text + "-" + text2; } private static string FormatTimestamp(DateTime timestampUtc) { return timestampUtc.ToUniversalTime().ToString("yyyy-MM-dd'T'HH:mm:ss.fff'Z'", CultureInfo.InvariantCulture); } } } namespace SkipDropshipCompany.Interop.Game { internal sealed class GameInterop : IGameInterop { private readonly NetworkAdapter networkAdapter; private readonly RoundAdapter roundAdapter; private readonly TerminalAdapter terminalAdapter; private readonly ItemSpawnAdapter itemSpawnAdapter; public GameInterop(IPluginLogger logger) { networkAdapter = new NetworkAdapter(logger); roundAdapter = new RoundAdapter(logger); terminalAdapter = new TerminalAdapter(logger); itemSpawnAdapter = new ItemSpawnAdapter(logger); } public bool IsServer() { return networkAdapter.IsServer(); } public RoundState GetRoundState() { return roundAdapter.GetRoundState(); } public string? GetCurrentLevelSceneName() { return roundAdapter.GetCurrentLevelSceneName(); } public List<int>? GetTerminalOrderedItemIndexes() { return terminalAdapter.GetOrderedItemIndexes(); } public bool SetTerminalOrderedItemIndexes(List<int> boughtItemIndexes) { return terminalAdapter.SetOrderedItemIndexes(boughtItemIndexes); } public bool SpawnBuyableItemInShip(int buyableItemIndex) { Item buyableItemByIndex = terminalAdapter.GetBuyableItemByIndex(buyableItemIndex); if ((Object)(object)buyableItemByIndex == (Object)null) { return false; } return itemSpawnAdapter.SpawnItemInShip(buyableItemByIndex); } } } namespace SkipDropshipCompany.Interop.Game.Patches { internal sealed class HarmonyCallbackDiagnosticReporter { private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public HarmonyCallbackDiagnosticReporter(IPluginLogger logger, IValidationLogger validationLogger) { this.logger = logger; this.validationLogger = validationLogger; } public void RecordCallbackException(string callback, Exception exception) { string text = exception.GetType().FullName ?? exception.GetType().Name; logger.LogError("Harmony callback exception: callback=" + callback + ", exception_type=" + text); validationLogger.Record(ValidationLogRecord.CallbackException(callback, text)); } } internal static class HarmonyCallbackGuard { private static HarmonyCallbackDiagnosticReporter? diagnosticReporter; public static void Configure(HarmonyCallbackDiagnosticReporter reporter) { diagnosticReporter = reporter; } public static bool TryNotifyHarmonyCallback(string callback, Action notify) { try { notify(); return true; } catch (Exception exception) { TryRecordCallbackException(callback, exception); return false; } } public static bool TryNotifyHarmonyCallback<T>(string callback, Func<T> notify, out T? result) { try { result = notify(); return true; } catch (Exception exception) { result = default(T); TryRecordCallbackException(callback, exception); return false; } } private static void TryRecordCallbackException(string callback, Exception exception) { try { diagnosticReporter?.RecordCallbackException(callback, exception); } catch { } } } internal static class HarmonyCallbackTokens { public const string TerminalSyncGroupCreditsClientRpcPrefix = "terminal_sync_group_credits_client_rpc_prefix"; public const string TerminalSyncGroupCreditsClientRpcPostfix = "terminal_sync_group_credits_client_rpc_postfix"; public const string StartOfRoundStartGamePostfix = "start_of_round_start_game_postfix"; public const string StartOfRoundResetShipPostfix = "start_of_round_reset_ship_postfix"; } internal static class HarmonyPatchInstaller { private static readonly Harmony harmony = new Harmony("com.aoirint.SkipDropshipCompany"); public static void Install() { harmony.PatchAll(typeof(HarmonyPatchInstaller).Assembly); } } [HarmonyPatch(typeof(StartOfRound))] internal static class StartOfRoundPatch { [HarmonyPatch("StartGame")] [HarmonyPostfix] public static void StartGamePostfix() { HarmonyCallbackGuard.TryNotifyHarmonyCallback("start_of_round_start_game_postfix", delegate { SkipDropshipCompany.Controller.HandleStartGame(); }); } [HarmonyPatch("ResetShip")] [HarmonyPostfix] public static void ResetShipPostfix() { HarmonyCallbackGuard.TryNotifyHarmonyCallback("start_of_round_reset_ship_postfix", delegate { SkipDropshipCompany.Controller.HandleResetShip(); }); } } [HarmonyPatch(typeof(Terminal))] internal static class TerminalPatch { [HarmonyPatch("SyncGroupCreditsClientRpc")] [HarmonyPrefix] public static void SyncGroupCreditsClientRpcPrefix(Terminal __instance, int newGroupCredits, ref int numItemsInShip) { if (HarmonyCallbackGuard.TryNotifyHarmonyCallback("terminal_sync_group_credits_client_rpc_prefix", () => SkipDropshipCompany.Controller.HandleTerminalSyncGroupCreditsClientRpcPrefix(), out PrepareInstantPurchaseResult result) && result != null) { numItemsInShip = result.DropShipBoughtItemIndexes.Count; } } [HarmonyPatch("SyncGroupCreditsClientRpc")] [HarmonyPostfix] public static void SyncGroupCreditsClientRpcPostfix() { HarmonyCallbackGuard.TryNotifyHarmonyCallback("terminal_sync_group_credits_client_rpc_postfix", delegate { SkipDropshipCompany.Controller.HandleTerminalSyncGroupCreditsClientRpcPostfix(); }); } } } namespace SkipDropshipCompany.Interop.Game.Adapters { internal sealed class ItemSpawnAdapter { private readonly IPluginLogger logger; private readonly Dictionary<int, float> cachedSpawnOffsetXByItemId = new Dictionary<int, float>(); public ItemSpawnAdapter(IPluginLogger logger) { this.logger = logger; } public bool SpawnItemInShip(Item item) { //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a9: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00b3: Unknown result type (might be due to invalid IL or missing references) //IL_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) StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { logger.LogError("StartOfRound.Instance is null."); return false; } GameObject spawnPrefab = item.spawnPrefab; if ((Object)(object)spawnPrefab == (Object)null) { logger.LogError("Item.spawnPrefab is null."); return false; } Transform elevatorTransform = instance.elevatorTransform; if ((Object)(object)elevatorTransform == (Object)null) { logger.LogError("StartOfRound.Instance.elevatorTransform is null."); return false; } Vector3? baseSpawnPosition = GetBaseSpawnPosition(instance); if (!baseSpawnPosition.HasValue) { logger.LogError("Failed to get spawn position."); return false; } Vector3 value = baseSpawnPosition.Value; float spawnOffsetXByItemId = GetSpawnOffsetXByItemId(item.itemId); Vector3 val = value + new Vector3(spawnOffsetXByItemId, 0.5f, 1f); GameObject val2 = Object.Instantiate<GameObject>(spawnPrefab, val, Quaternion.identity, elevatorTransform); GrabbableObject component = val2.GetComponent<GrabbableObject>(); if ((Object)(object)component == (Object)null) { logger.LogError("Failed to get GrabbableObject component from spawned item."); Object.Destroy((Object)(object)val2); return false; } component.fallTime = 0f; component.isInElevator = true; component.isInShipRoom = true; component.hasHitGround = true; NetworkObject component2 = val2.GetComponent<NetworkObject>(); if ((Object)(object)component2 == (Object)null) { logger.LogError("Failed to get NetworkObject component from spawned item."); Object.Destroy((Object)(object)val2); return false; } component2.Spawn(false); return true; } private Vector3? GetBaseSpawnPosition(StartOfRound startOfRound) { //IL_0050: Unknown result type (might be due to invalid IL or missing references) Transform[] playerSpawnPositions = startOfRound.playerSpawnPositions; if (playerSpawnPositions == null) { logger.LogError("StartOfRound.Instance.playerSpawnPositions is null."); return null; } Transform val = playerSpawnPositions.ElementAtOrDefault(1); if ((Object)(object)val == (Object)null) { logger.LogError("Player spawn position is null for ID 1."); return null; } return val.position; } private float GetSpawnOffsetXByItemId(int itemId) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) if (cachedSpawnOffsetXByItemId.TryGetValue(itemId, out var value)) { return value; } uint num = math.hash(new uint4((uint)itemId, 3735928559u, 305419896u, 2271560481u)); if (num == 0) { num = 1u; } Random val = default(Random); ((Random)(ref val))..ctor(num); float num2 = ((Random)(ref val)).NextFloat(-0.7f, 0.7f); cachedSpawnOffsetXByItemId[itemId] = num2; logger.LogDebug($"Generated offset X for an item ID. itemId={itemId}, offsetX={num2}"); return num2; } } internal sealed class NetworkAdapter { private readonly IPluginLogger logger; public NetworkAdapter(IPluginLogger logger) { this.logger = logger; } public bool IsServer() { NetworkManager singleton = NetworkManager.Singleton; if ((Object)(object)singleton == (Object)null) { logger.LogError("NetworkManager.Singleton is null."); return false; } return singleton.IsServer; } } internal sealed class RoundAdapter { private readonly IPluginLogger logger; public RoundAdapter(IPluginLogger logger) { this.logger = logger; } public RoundState GetRoundState() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { logger.LogError("StartOfRound.Instance is null."); return new RoundState(isInOrbit: false, isFirstDay: false, isRoutingToCompany: false); } bool inShipPhase = instance.inShipPhase; bool isFirstDay = IsFirstDay(instance); bool isRoutingToCompany = IsRoutingToCompany(instance); return new RoundState(inShipPhase, isFirstDay, isRoutingToCompany); } public string? GetCurrentLevelSceneName() { StartOfRound instance = StartOfRound.Instance; if ((Object)(object)instance == (Object)null) { logger.LogError("StartOfRound.Instance is null."); return null; } return instance.currentLevel?.sceneName; } private bool IsFirstDay(StartOfRound startOfRound) { EndOfGameStats gameStats = startOfRound.gameStats; if (gameStats == null) { logger.LogError("StartOfRound.Instance.gameStats is null."); return false; } int daysSpent = gameStats.daysSpent; logger.LogDebug($"daysSpent={daysSpent}"); return daysSpent == 0; } private bool IsRoutingToCompany(StartOfRound startOfRound) { SelectableLevel currentLevel = startOfRound.currentLevel; if ((Object)(object)currentLevel == (Object)null) { logger.LogError("StartOfRound.Instance.currentLevel is null."); return false; } string sceneName = currentLevel.sceneName; logger.LogDebug("IsSceneNameCompany? sceneName=" + sceneName); return CompanyScene.IsCompanyScene(sceneName); } } internal sealed class TerminalAdapter { private readonly IPluginLogger logger; private Terminal? cachedTerminal; public TerminalAdapter(IPluginLogger logger) { this.logger = logger; } public Item? GetBuyableItemByIndex(int index) { Terminal terminal = GetTerminal(); if ((Object)(object)terminal == (Object)null) { logger.LogError("Terminal is null."); return null; } Item[] buyableItemsList = terminal.buyableItemsList; if (buyableItemsList == null) { logger.LogError("Terminal.buyableItemsList is null."); return null; } return buyableItemsList.ElementAtOrDefault(index); } public List<int>? GetOrderedItemIndexes() { Terminal terminal = GetTerminal(); if ((Object)(object)terminal == (Object)null) { return null; } return terminal.orderedItemsFromTerminal; } public bool SetOrderedItemIndexes(List<int> boughtItemIndexes) { Terminal terminal = GetTerminal(); if ((Object)(object)terminal == (Object)null) { return false; } terminal.orderedItemsFromTerminal = boughtItemIndexes; return true; } private Terminal? GetTerminal() { if ((Object)(object)cachedTerminal != (Object)null) { return cachedTerminal; } Terminal val = Object.FindObjectOfType<Terminal>(); if ((Object)(object)val == (Object)null) { logger.LogError("Failed to find Terminal instance in the scene."); return null; } cachedTerminal = val; return val; } } } namespace SkipDropshipCompany.Core.Validation { internal sealed class DisabledValidationLogger : IValidationLogger { public static DisabledValidationLogger Instance { get; } = new DisabledValidationLogger(); private DisabledValidationLogger() { } public void Record(ValidationLogRecord record) { } } internal enum ValidationLogRole { Server, Client } internal enum ValidationLogScene { Company, Other, Unknown } internal enum ValidationLogPrepareResult { NoServer, NotAllowed, Success } internal enum ValidationLogSpawnResult { NoServer, NoPreparedPurchase, SpawnFailed, Success } internal enum ValidationLogLandingHistoryResult { NoServer, NullScene, EmptyScene, Success } internal enum ValidationLogTerminalOrderReadResult { NullOrderedItems } internal enum ValidationLogTerminalOrderRestoreResult { Failed, Success } internal sealed class ValidationLogRecord { public string EventName { get; } public Dictionary<string, object?>? Fields { get; } private ValidationLogRecord(string eventName, Dictionary<string, object?>? fields = null) { EventName = eventName; Fields = fields; } public static ValidationLogRecord PluginLoaded(string version, bool validationLogging, bool enabled, bool requireReroutingOnFirstDay) { return new ValidationLogRecord("plugin_loaded", new Dictionary<string, object> { ["version"] = version, ["validation_logging"] = validationLogging, ["enabled"] = enabled, ["require_rerouting_on_first_day"] = requireReroutingOnFirstDay }); } public static ValidationLogRecord ControllerCreated() { return new ValidationLogRecord("controller_created"); } public static ValidationLogRecord CallbackException(string callback, string exceptionType) { return new ValidationLogRecord("callback_exception", new Dictionary<string, object> { ["callback"] = callback, ["exception_type"] = exceptionType }); } public static ValidationLogRecord InstantPurchaseEligibilityDecision(RoundState roundState, bool enabled, bool requireReroutingOnFirstDay, bool lastLandedOnCompany, bool allowed, string reason) { return new ValidationLogRecord("instant_purchase_eligibility_decision", new Dictionary<string, object> { ["enabled"] = enabled, ["require_rerouting_on_first_day"] = requireReroutingOnFirstDay, ["is_in_orbit"] = roundState.IsInOrbit, ["is_first_day"] = roundState.IsFirstDay, ["is_routing_to_company"] = roundState.IsRoutingToCompany, ["last_landed_on_company"] = lastLandedOnCompany, ["allowed"] = allowed, ["reason"] = reason }); } public static ValidationLogRecord InstantPurchaseEligibilityConfigDisabled() { return new ValidationLogRecord("instant_purchase_eligibility_decision", new Dictionary<string, object> { ["enabled"] = false, ["allowed"] = false, ["reason"] = "disabled" }); } public static ValidationLogRecord PrepareInstantPurchaseResult(ValidationLogRole role, ValidationLogPrepareResult result, int originalItemCount, PrepareInstantPurchaseResult? preparedResult) { return new ValidationLogRecord("prepare_instant_purchase_result", new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role), ["result"] = ToValidationPrepareResultToken(result), ["original_item_count"] = originalItemCount, ["dropship_item_count"] = preparedResult?.DropShipBoughtItemIndexes.Count, ["instant_item_count"] = preparedResult?.InstantBoughtItemIndexes.Count }); } public static ValidationLogRecord SpawnInstantPurchaseResult(ValidationLogRole role, ValidationLogSpawnResult result, int preparedInstantItemCount, int preparedDropShipItemCount, int spawnedItemCount) { return new ValidationLogRecord("spawn_instant_purchase_result", new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role), ["result"] = ToValidationSpawnResultToken(result), ["prepared_instant_item_count"] = preparedInstantItemCount, ["prepared_dropship_item_count"] = preparedDropShipItemCount, ["spawned_item_count"] = spawnedItemCount }); } public static ValidationLogRecord LandingHistoryUpdated(ValidationLogRole role, ValidationLogLandingHistoryResult result, ValidationLogScene scene) { return new ValidationLogRecord("landing_history_updated", new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role), ["result"] = ToValidationLandingHistoryResultToken(result), ["scene"] = ToValidationSceneToken(scene) }); } public static ValidationLogRecord LandingHistoryCleared(ValidationLogRole role, bool cleared) { return new ValidationLogRecord("landing_history_cleared", new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role), ["cleared"] = cleared }); } public static ValidationLogRecord TerminalOrderReadResult(ValidationLogRole role, ValidationLogTerminalOrderReadResult result) { return new ValidationLogRecord("terminal_order_read_result", new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role), ["result"] = ToValidationTerminalOrderReadResultToken(result) }); } public static ValidationLogRecord TerminalOrderRestoreResult(ValidationLogRole role, ValidationLogTerminalOrderRestoreResult result, int dropshipItemCount) { return new ValidationLogRecord("terminal_order_restore_result", new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role), ["result"] = ToValidationTerminalOrderRestoreResultToken(result), ["dropship_item_count"] = dropshipItemCount }); } public static ValidationLogScene ToValidationScene(string? sceneName) { if (sceneName == null) { return ValidationLogScene.Unknown; } if (!CompanyScene.IsCompanyScene(sceneName)) { return ValidationLogScene.Other; } return ValidationLogScene.Company; } private static string ToValidationRoleToken(ValidationLogRole role) { return role switch { ValidationLogRole.Server => "server", ValidationLogRole.Client => "client", _ => "unknown", }; } private static string ToValidationSceneToken(ValidationLogScene scene) { return scene switch { ValidationLogScene.Company => "company", ValidationLogScene.Other => "other", ValidationLogScene.Unknown => "unknown", _ => "unknown", }; } private static string ToValidationPrepareResultToken(ValidationLogPrepareResult result) { return result switch { ValidationLogPrepareResult.NoServer => "no_server", ValidationLogPrepareResult.NotAllowed => "not_allowed", ValidationLogPrepareResult.Success => "success", _ => "unknown", }; } private static string ToValidationSpawnResultToken(ValidationLogSpawnResult result) { return result switch { ValidationLogSpawnResult.NoServer => "no_server", ValidationLogSpawnResult.NoPreparedPurchase => "no_prepared_purchase", ValidationLogSpawnResult.SpawnFailed => "spawn_failed", ValidationLogSpawnResult.Success => "success", _ => "unknown", }; } private static string ToValidationLandingHistoryResultToken(ValidationLogLandingHistoryResult result) { return result switch { ValidationLogLandingHistoryResult.NoServer => "no_server", ValidationLogLandingHistoryResult.NullScene => "null_scene", ValidationLogLandingHistoryResult.EmptyScene => "empty_scene", ValidationLogLandingHistoryResult.Success => "success", _ => "unknown", }; } private static string ToValidationTerminalOrderReadResultToken(ValidationLogTerminalOrderReadResult result) { if (result == ValidationLogTerminalOrderReadResult.NullOrderedItems) { return "null_ordered_items"; } return "unknown"; } private static string ToValidationTerminalOrderRestoreResultToken(ValidationLogTerminalOrderRestoreResult result) { return result switch { ValidationLogTerminalOrderRestoreResult.Failed => "failed", ValidationLogTerminalOrderRestoreResult.Success => "success", _ => "unknown", }; } } } namespace SkipDropshipCompany.Core.UseCases { internal sealed class ClearLandingHistoryUseCase { private readonly IGameInterop gameInterop; private readonly LandingHistoryStore landingHistoryStore; private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public ClearLandingHistoryUseCase(IGameInterop gameInterop, LandingHistoryStore landingHistoryStore, IPluginLogger logger, IValidationLogger validationLogger) { this.gameInterop = gameInterop; this.landingHistoryStore = landingHistoryStore; this.logger = logger; this.validationLogger = validationLogger; } public void Execute() { if (!gameInterop.IsServer()) { logger.LogDebug("Not the server. Skipping landing history clear."); validationLogger.Record(ValidationLogRecord.LandingHistoryCleared(ValidationLogRole.Client, cleared: false)); return; } logger.LogDebug("Clearing landing history."); landingHistoryStore.ClearLandingHistory(); logger.LogDebug("Cleared landing history."); validationLogger.Record(ValidationLogRecord.LandingHistoryCleared(ValidationLogRole.Server, cleared: true)); } } internal sealed class InstantPurchaseEligibilityUseCase { private readonly IPluginConfig config; private readonly IGameInterop gameInterop; private readonly LandingHistoryStore landingHistoryStore; private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public InstantPurchaseEligibilityUseCase(IPluginConfig config, IGameInterop gameInterop, LandingHistoryStore landingHistoryStore, IPluginLogger logger, IValidationLogger validationLogger) { this.config = config; this.gameInterop = gameInterop; this.landingHistoryStore = landingHistoryStore; this.logger = logger; this.validationLogger = validationLogger; } public bool IsInstantPurchaseAllowed() { logger.LogDebug("Checking if instant purchase is allowed."); if (!config.Enabled) { logger.LogDebug("Not enabled."); validationLogger.Record(ValidationLogRecord.InstantPurchaseEligibilityConfigDisabled()); return false; } bool requireReroutingOnFirstDay = config.RequireReroutingOnFirstDay; logger.LogDebug($"Configs: isFirstDayRerouteRequired={requireReroutingOnFirstDay}"); RoundState roundState = gameInterop.GetRoundState(); bool flag = IsLandedOnCompany(roundState); bool flag2 = IsInFirstDayOrbit(roundState); bool flag3 = IsInFirstDayOrbitAndRoutingToCompany(roundState); bool lastLandedOnCompany = roundState.IsInOrbit && landingHistoryStore.IsLastLandedOnCompany(); bool flag4 = IsInOrbitAndLastLandedOnCompanyAndRoutingToCompany(roundState, lastLandedOnCompany); logger.LogDebug("Flags:" + $" IsLandedOnCompany={flag}" + $" IsInFirstDayOrbit={flag2}" + $" IsInFirstDayOrbitAndRoutingToCompany={flag3}" + $" isInOrbitAndLastLandedOnCompanyAndRoutingToCompany={flag4}"); bool flag5 = flag || (!requireReroutingOnFirstDay && flag2) || flag3 || flag4; validationLogger.Record(ValidationLogRecord.InstantPurchaseEligibilityDecision(roundState, enabled: true, requireReroutingOnFirstDay, lastLandedOnCompany, flag5, GetEligibilityReason(flag, requireReroutingOnFirstDay, flag2, flag3, flag4))); return flag5; } private bool IsInFirstDayOrbit(RoundState roundState) { if (!roundState.IsInOrbit) { logger.LogDebug("Not in orbit."); return false; } if (!roundState.IsFirstDay) { logger.LogDebug("Not first day."); return false; } return true; } private bool IsInFirstDayOrbitAndRoutingToCompany(RoundState roundState) { if (!IsInFirstDayOrbit(roundState)) { logger.LogDebug("Not in first day orbit."); return false; } if (!roundState.IsRoutingToCompany) { logger.LogDebug("Not routing to company."); return false; } return true; } private bool IsLandedOnCompany(RoundState roundState) { if (roundState.IsInOrbit) { logger.LogDebug("In orbit."); return false; } if (!roundState.IsRoutingToCompany) { logger.LogDebug("Not routing to company."); return false; } return true; } private bool IsInOrbitAndLastLandedOnCompanyAndRoutingToCompany(RoundState roundState, bool lastLandedOnCompany) { if (!roundState.IsInOrbit) { logger.LogDebug("Not in orbit."); return false; } if (!lastLandedOnCompany) { logger.LogDebug("Last landed level is not company."); return false; } if (!roundState.IsRoutingToCompany) { logger.LogDebug("Not routing to company."); return false; } return true; } private static string GetEligibilityReason(bool isLandedOnCompany, bool isFirstDayRerouteRequired, bool isInFirstDayOrbit, bool isInFirstDayOrbitAndRoutingToCompany, bool isInOrbitAndLastLandedOnCompanyAndRoutingToCompany) { if (isLandedOnCompany) { return "landed_on_company"; } if (!isFirstDayRerouteRequired && isInFirstDayOrbit) { return "first_day_orbit"; } if (isInFirstDayOrbitAndRoutingToCompany) { return "first_day_orbit_routing_to_company"; } if (isInOrbitAndLastLandedOnCompanyAndRoutingToCompany) { return "orbit_after_company_landing"; } return "conditions_not_met"; } } internal sealed class PrepareInstantPurchaseUseCase { private readonly IGameInterop gameInterop; private readonly InstantPurchaseEligibilityUseCase eligibilityUseCase; private readonly PreparedInstantPurchaseStore preparedInstantPurchaseStore; private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public PrepareInstantPurchaseUseCase(IGameInterop gameInterop, InstantPurchaseEligibilityUseCase eligibilityUseCase, PreparedInstantPurchaseStore preparedInstantPurchaseStore, IPluginLogger logger, IValidationLogger validationLogger) { this.gameInterop = gameInterop; this.eligibilityUseCase = eligibilityUseCase; this.preparedInstantPurchaseStore = preparedInstantPurchaseStore; this.logger = logger; this.validationLogger = validationLogger; } public PrepareInstantPurchaseResult? Execute(List<int> boughtItemIndexes) { if (!gameInterop.IsServer()) { logger.LogDebug("Not the server. Skipping instant purchase logic."); validationLogger.Record(ValidationLogRecord.PrepareInstantPurchaseResult(ValidationLogRole.Client, ValidationLogPrepareResult.NoServer, boughtItemIndexes.Count, null)); return null; } logger.LogDebug("Preparing instant purchase."); if (!eligibilityUseCase.IsInstantPurchaseAllowed()) { logger.LogDebug("Instant purchase is not allowed in the current game state."); validationLogger.Record(ValidationLogRecord.PrepareInstantPurchaseResult(ValidationLogRole.Server, ValidationLogPrepareResult.NotAllowed, boughtItemIndexes.Count, null)); return null; } PrepareInstantPurchaseResult prepareInstantPurchaseResult = new PrepareInstantPurchaseResult(new List<int>(), boughtItemIndexes); validationLogger.Record(ValidationLogRecord.PrepareInstantPurchaseResult(ValidationLogRole.Server, ValidationLogPrepareResult.Success, boughtItemIndexes.Count, prepareInstantPurchaseResult)); preparedInstantPurchaseStore.SetPreparedInstantPurchaseResult(prepareInstantPurchaseResult); return prepareInstantPurchaseResult; } } internal sealed class RecordLandingUseCase { private readonly IGameInterop gameInterop; private readonly LandingHistoryStore landingHistoryStore; private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public RecordLandingUseCase(IGameInterop gameInterop, LandingHistoryStore landingHistoryStore, IPluginLogger logger, IValidationLogger validationLogger) { this.gameInterop = gameInterop; this.landingHistoryStore = landingHistoryStore; this.logger = logger; this.validationLogger = validationLogger; } public void Execute(string? sceneName) { if (!gameInterop.IsServer()) { logger.LogDebug("Not the server. Skipping landing history addition."); validationLogger.Record(ValidationLogRecord.LandingHistoryUpdated(ValidationLogRole.Client, ValidationLogLandingHistoryResult.NoServer, ValidationLogRecord.ToValidationScene(sceneName))); return; } if (sceneName == null) { logger.LogError("StartOfRound.currentLevel.sceneName is null."); validationLogger.Record(ValidationLogRecord.LandingHistoryUpdated(ValidationLogRole.Server, ValidationLogLandingHistoryResult.NullScene, ValidationLogScene.Unknown)); return; } logger.LogDebug("Adding landing history. sceneName=" + sceneName); if (!landingHistoryStore.AddLandingHistory(sceneName)) { logger.LogError("Failed to add landing history. sceneName=" + sceneName); validationLogger.Record(ValidationLogRecord.LandingHistoryUpdated(ValidationLogRole.Server, ValidationLogLandingHistoryResult.EmptyScene, ValidationLogRecord.ToValidationScene(sceneName))); } else { logger.LogDebug("Added landing history. sceneName=" + sceneName); validationLogger.Record(ValidationLogRecord.LandingHistoryUpdated(ValidationLogRole.Server, ValidationLogLandingHistoryResult.Success, ValidationLogRecord.ToValidationScene(sceneName))); } } } internal sealed class SpawnPreparedInstantPurchasedItemsUseCase { private readonly IGameInterop gameInterop; private readonly PreparedInstantPurchaseStore preparedInstantPurchaseStore; private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public SpawnPreparedInstantPurchasedItemsUseCase(IGameInterop gameInterop, PreparedInstantPurchaseStore preparedInstantPurchaseStore, IPluginLogger logger, IValidationLogger validationLogger) { this.gameInterop = gameInterop; this.preparedInstantPurchaseStore = preparedInstantPurchaseStore; this.logger = logger; this.validationLogger = validationLogger; } public SpawnPreparedInstantPurchasedItemsResult? Execute() { if (!gameInterop.IsServer()) { logger.LogDebug("Not the server. Skipping instant purchase logic."); validationLogger.Record(ValidationLogRecord.SpawnInstantPurchaseResult(ValidationLogRole.Client, ValidationLogSpawnResult.NoServer, 0, 0, 0)); return null; } logger.LogDebug("Spawning prepared instant purchased items."); PrepareInstantPurchaseResult preparedInstantPurchaseResult = preparedInstantPurchaseStore.GetPreparedInstantPurchaseResult(); if (preparedInstantPurchaseResult == null) { logger.LogDebug("No prepared instant purchase to spawn."); validationLogger.Record(ValidationLogRecord.SpawnInstantPurchaseResult(ValidationLogRole.Server, ValidationLogSpawnResult.NoPreparedPurchase, 0, 0, 0)); return null; } int num = 0; foreach (int instantBoughtItemIndex in preparedInstantPurchaseResult.InstantBoughtItemIndexes) { if (!gameInterop.SpawnBuyableItemInShip(instantBoughtItemIndex)) { logger.LogError($"Failed to spawn instant purchased item. buyableItemIndex={instantBoughtItemIndex}"); validationLogger.Record(ValidationLogRecord.SpawnInstantPurchaseResult(ValidationLogRole.Server, ValidationLogSpawnResult.SpawnFailed, preparedInstantPurchaseResult.InstantBoughtItemIndexes.Count, preparedInstantPurchaseResult.DropShipBoughtItemIndexes.Count, num)); return null; } num++; } SpawnPreparedInstantPurchasedItemsResult result = new SpawnPreparedInstantPurchasedItemsResult(preparedInstantPurchaseResult.DropShipBoughtItemIndexes, preparedInstantPurchaseResult.InstantBoughtItemIndexes); preparedInstantPurchaseStore.ClearPreparedInstantPurchaseResult(); validationLogger.Record(ValidationLogRecord.SpawnInstantPurchaseResult(ValidationLogRole.Server, ValidationLogSpawnResult.Success, preparedInstantPurchaseResult.InstantBoughtItemIndexes.Count, preparedInstantPurchaseResult.DropShipBoughtItemIndexes.Count, num)); return result; } } internal sealed class PrepareInstantPurchaseResult { public List<int> DropShipBoughtItemIndexes { get; } public List<int> InstantBoughtItemIndexes { get; } public PrepareInstantPurchaseResult(List<int> dropShipBoughtItemIndexes, List<int> instantBoughtItemIndexes) { DropShipBoughtItemIndexes = dropShipBoughtItemIndexes; InstantBoughtItemIndexes = instantBoughtItemIndexes; } } internal sealed class SpawnPreparedInstantPurchasedItemsResult { public List<int> DropShipBoughtItemIndexes { get; } public List<int> InstantBoughtItemIndexes { get; } public SpawnPreparedInstantPurchasedItemsResult(List<int> dropShipBoughtItemIndexes, List<int> instantBoughtItemIndexes) { DropShipBoughtItemIndexes = dropShipBoughtItemIndexes; InstantBoughtItemIndexes = instantBoughtItemIndexes; } } } namespace SkipDropshipCompany.Core.State { internal static class CompanyScene { private const string CompanySceneName = "CompanyBuilding"; public static bool IsCompanyScene(string sceneName) { return sceneName == "CompanyBuilding"; } } internal sealed class LandingHistoryStore { private const int LandingHistorySize = 1; private readonly IPluginLogger logger; private List<string> landingEntries = new List<string>(); public LandingHistoryStore(IPluginLogger logger) { this.logger = logger; } public bool AddLandingHistory(string sceneName) { if (string.IsNullOrEmpty(sceneName)) { logger.LogError("Scene name is null or empty. Cannot add to landing history."); return false; } landingEntries.Add(sceneName); landingEntries = landingEntries.TakeLast(1).ToList(); logger.LogDebug("Updated landing history. landingEntries=" + string.Join(", ", landingEntries)); return true; } public bool IsLastLandedOnCompany() { string text = landingEntries.LastOrDefault(); if (text == null) { logger.LogDebug("Last landed scene name is null."); return false; } if (!CompanyScene.IsCompanyScene(text)) { logger.LogDebug("Last landed scene is not company."); return false; } return true; } public void ClearLandingHistory() { landingEntries.Clear(); } } internal sealed class PreparedInstantPurchaseStore { private PrepareInstantPurchaseResult? preparedInstantPurchaseResult; public PrepareInstantPurchaseResult? GetPreparedInstantPurchaseResult() { return preparedInstantPurchaseResult; } public void SetPreparedInstantPurchaseResult(PrepareInstantPurchaseResult result) { preparedInstantPurchaseResult = result; } public void ClearPreparedInstantPurchaseResult() { preparedInstantPurchaseResult = null; } } internal sealed class RoundState { public bool IsInOrbit { get; } public bool IsFirstDay { get; } public bool IsRoutingToCompany { get; } public RoundState(bool isInOrbit, bool isFirstDay, bool isRoutingToCompany) { IsInOrbit = isInOrbit; IsFirstDay = isFirstDay; IsRoutingToCompany = isRoutingToCompany; } } } namespace SkipDropshipCompany.Core.Ports { internal interface IGameInterop { bool IsServer(); RoundState GetRoundState(); string? GetCurrentLevelSceneName(); List<int>? GetTerminalOrderedItemIndexes(); bool SetTerminalOrderedItemIndexes(List<int> boughtItemIndexes); bool SpawnBuyableItemInShip(int buyableItemIndex); } internal interface IPluginConfig { bool Enabled { get; } bool RequireReroutingOnFirstDay { get; } bool ValidationLogging { get; } } internal interface IPluginLogger { void LogDebug(string message); void LogInfo(string message); void LogError(string message); } internal interface IValidationLogger { void Record(ValidationLogRecord record); } } namespace SkipDropshipCompany.Core.Handlers { internal sealed class RoundCallbackHandler { private readonly IGameInterop gameInterop; private readonly RecordLandingUseCase recordLandingUseCase; private readonly ClearLandingHistoryUseCase clearLandingHistoryUseCase; public RoundCallbackHandler(IGameInterop gameInterop, RecordLandingUseCase recordLandingUseCase, ClearLandingHistoryUseCase clearLandingHistoryUseCase) { this.gameInterop = gameInterop; this.recordLandingUseCase = recordLandingUseCase; this.clearLandingHistoryUseCase = clearLandingHistoryUseCase; } public void HandleStartGame() { recordLandingUseCase.Execute(gameInterop.GetCurrentLevelSceneName()); } public void HandleResetShip() { clearLandingHistoryUseCase.Execute(); } } internal sealed class TerminalSyncGroupCreditsHandler { private readonly IGameInterop gameInterop; private readonly PrepareInstantPurchaseUseCase prepareInstantPurchaseUseCase; private readonly SpawnPreparedInstantPurchasedItemsUseCase spawnPreparedInstantPurchasedItemsUseCase; private readonly IPluginLogger logger; private readonly IValidationLogger validationLogger; public TerminalSyncGroupCreditsHandler(IGameInterop gameInterop, PrepareInstantPurchaseUseCase prepareInstantPurchaseUseCase, SpawnPreparedInstantPurchasedItemsUseCase spawnPreparedInstantPurchasedItemsUseCase, IPluginLogger logger, IValidationLogger validationLogger) { this.gameInterop = gameInterop; this.prepareInstantPurchaseUseCase = prepareInstantPurchaseUseCase; this.spawnPreparedInstantPurchasedItemsUseCase = spawnPreparedInstantPurchasedItemsUseCase; this.logger = logger; this.validationLogger = validationLogger; } public PrepareInstantPurchaseResult? HandlePrefix() { List<int> terminalOrderedItemIndexes = gameInterop.GetTerminalOrderedItemIndexes(); if (terminalOrderedItemIndexes == null) { logger.LogError("Terminal.orderedItemsFromTerminal is null."); validationLogger.Record(ValidationLogRecord.TerminalOrderReadResult(GetRole(), ValidationLogTerminalOrderReadResult.NullOrderedItems)); return null; } PrepareInstantPurchaseResult prepareInstantPurchaseResult = prepareInstantPurchaseUseCase.Execute(terminalOrderedItemIndexes); if (prepareInstantPurchaseResult == null) { logger.LogDebug("Prepare instant purchase failed or not allowed. Skipping instant purchase logic."); return null; } logger.LogDebug("Prepared instant purchase." + $" originalDropShipItemCount={terminalOrderedItemIndexes.Count}" + $" preparedDropShipItemCount={prepareInstantPurchaseResult.DropShipBoughtItemIndexes.Count}" + $" preparedInstantPurchaseItemCount={prepareInstantPurchaseResult.InstantBoughtItemIndexes.Count}"); return prepareInstantPurchaseResult; } public void HandlePostfix() { SpawnPreparedInstantPurchasedItemsResult spawnPreparedInstantPurchasedItemsResult = spawnPreparedInstantPurchasedItemsUseCase.Execute(); if (spawnPreparedInstantPurchasedItemsResult == null) { logger.LogDebug("Spawning prepared instant purchased items failed or none to spawn."); } else if (!gameInterop.SetTerminalOrderedItemIndexes(spawnPreparedInstantPurchasedItemsResult.DropShipBoughtItemIndexes)) { logger.LogError("Failed to restore Terminal.orderedItemsFromTerminal."); validationLogger.Record(ValidationLogRecord.TerminalOrderRestoreResult(GetRole(), ValidationLogTerminalOrderRestoreResult.Failed, spawnPreparedInstantPurchasedItemsResult.DropShipBoughtItemIndexes.Count)); } else { validationLogger.Record(ValidationLogRecord.TerminalOrderRestoreResult(GetRole(), ValidationLogTerminalOrderRestoreResult.Success, spawnPreparedInstantPurchasedItemsResult.DropShipBoughtItemIndexes.Count)); logger.LogDebug("Spawned all prepared instant purchased items."); } } private ValidationLogRole GetRole() { if (!gameInterop.IsServer()) { return ValidationLogRole.Client; } return ValidationLogRole.Server; } } }