using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using CruiserJumpPractice.Core.Handlers;
using CruiserJumpPractice.Core.Ports;
using CruiserJumpPractice.Core.Presentation;
using CruiserJumpPractice.Core.Snapshots;
using CruiserJumpPractice.Core.State;
using CruiserJumpPractice.Core.UseCases;
using CruiserJumpPractice.Core.UseCases.Client;
using CruiserJumpPractice.Core.UseCases.Server;
using CruiserJumpPractice.Core.Validation;
using CruiserJumpPractice.Interop;
using CruiserJumpPractice.Interop.Game;
using CruiserJumpPractice.Interop.Game.Adapters;
using CruiserJumpPractice.Interop.Game.Behaviours;
using CruiserJumpPractice.Interop.Game.Patches;
using CruiserJumpPractice.Interop.InputUtils;
using GameNetcodeStuff;
using HarmonyLib;
using LethalCompanyInputUtils.Api;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
[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.CruiserJumpPractice")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: AssemblyInformationalVersion("0.2.0+e58abd29d5b04d7eefcb7d5ab4e9587839a48858")]
[assembly: AssemblyProduct("CruiserJumpPractice")]
[assembly: AssemblyTitle("com.aoirint.CruiserJumpPractice")]
[assembly: AssemblyVersion("0.2.0.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 CruiserJumpPractice
{
[BepInPlugin("com.aoirint.CruiserJumpPractice", "CruiserJumpPractice", "0.2.0")]
[BepInDependency(/*Could not decode attribute arguments.*/)]
[BepInProcess("Lethal Company.exe")]
public class CruiserJumpPractice : BaseUnityPlugin
{
private static PluginController? controller;
internal static PluginController Controller => controller;
private void Awake()
{
BepInExPluginLogger bepInExPluginLogger = new BepInExPluginLogger(((BaseUnityPlugin)this).Logger);
ConfigEntry<bool> val = ((BaseUnityPlugin)this).Config.Bind<bool>("Debug", "ValidationLogging", false, "Enable structured validation logs for release validation and troubleshooting.");
IValidationLogger validationLogger;
if (!val.Value)
{
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.0", val.Value));
controller = PluginController.Create(bepInExPluginLogger, validationLogger2);
HarmonyPatchInstaller.Install();
bepInExPluginLogger.LogInfo("Plugin CruiserJumpPractice v0.2.0 is loaded!");
}
}
internal sealed class PluginController
{
private readonly IGameInterop gameInterop;
private readonly IValidationLogger validationLogger;
private readonly FrameHandler frameHandler;
private readonly StartupHandler startupHandler;
private readonly BaseGameAppliedStateValidationHandler baseGameAppliedStateValidationHandler;
private readonly SaveCruiserStateUseCase saveCruiserStateUseCase;
private readonly LoadCruiserStateUseCase loadCruiserStateUseCase;
private readonly PresentSaveCruiserStateResultUseCase presentSaveCruiserStateResultUseCase;
private readonly PresentLoadCruiserStateResultUseCase presentLoadCruiserStateResultUseCase;
private PluginController(IGameInterop gameInterop, IValidationLogger validationLogger, FrameHandler frameHandler, StartupHandler startupHandler, BaseGameAppliedStateValidationHandler baseGameAppliedStateValidationHandler, SaveCruiserStateUseCase saveCruiserStateUseCase, LoadCruiserStateUseCase loadCruiserStateUseCase, PresentSaveCruiserStateResultUseCase presentSaveCruiserStateResultUseCase, PresentLoadCruiserStateResultUseCase presentLoadCruiserStateResultUseCase)
{
this.gameInterop = gameInterop;
this.validationLogger = validationLogger;
this.frameHandler = frameHandler;
this.startupHandler = startupHandler;
this.baseGameAppliedStateValidationHandler = baseGameAppliedStateValidationHandler;
this.saveCruiserStateUseCase = saveCruiserStateUseCase;
this.loadCruiserStateUseCase = loadCruiserStateUseCase;
this.presentSaveCruiserStateResultUseCase = presentSaveCruiserStateResultUseCase;
this.presentLoadCruiserStateResultUseCase = presentLoadCruiserStateResultUseCase;
}
public static PluginController Create(IPluginLogger logger, IValidationLogger validationLogger)
{
InputUtilsPracticeInput practiceInput = new InputUtilsPracticeInput(new InputUtilsActions());
IGameInterop gameInterop = new GameInterop(logger, validationLogger);
CruiserStateStore cruiserStateStore = new CruiserStateStore();
BaseGameAppliedStateValidationStore stateStore = new BaseGameAppliedStateValidationStore();
validationLogger.Record(ValidationLogRecord.StateStoreCreated());
SaveCruiserStateUseCase saveCruiserStateUseCase = new SaveCruiserStateUseCase(gameInterop, cruiserStateStore, logger, validationLogger);
LoadCruiserStateUseCase loadCruiserStateUseCase = new LoadCruiserStateUseCase(gameInterop, cruiserStateStore, logger, validationLogger);
RequestSaveCruiserStateUseCase requestSaveCruiserStateUseCase = new RequestSaveCruiserStateUseCase(gameInterop, validationLogger);
RequestLoadCruiserStateUseCase requestLoadCruiserStateUseCase = new RequestLoadCruiserStateUseCase(gameInterop, validationLogger);
ToggleMagnetUseCase toggleMagnetUseCase = new ToggleMagnetUseCase(gameInterop, validationLogger);
PresentSaveCruiserStateResultUseCase presentSaveCruiserStateResultUseCase = new PresentSaveCruiserStateResultUseCase(gameInterop, logger);
PresentLoadCruiserStateResultUseCase presentLoadCruiserStateResultUseCase = new PresentLoadCruiserStateResultUseCase(gameInterop, logger);
FrameHandler frameHandler = new FrameHandler(gameInterop, practiceInput, validationLogger, requestSaveCruiserStateUseCase, requestLoadCruiserStateUseCase, toggleMagnetUseCase);
validationLogger.Record(ValidationLogRecord.ControllerCreated());
return new PluginController(gameInterop, validationLogger, frameHandler, new StartupHandler(gameInterop, validationLogger), new BaseGameAppliedStateValidationHandler(gameInterop, validationLogger, stateStore), saveCruiserStateUseCase, loadCruiserStateUseCase, presentSaveCruiserStateResultUseCase, presentLoadCruiserStateResultUseCase);
}
public void HandleStartup()
{
startupHandler.HandleStartup();
}
public void HandleFrame()
{
frameHandler.HandleFrame();
}
public SaveCruiserStateResult SaveCruiserState()
{
return saveCruiserStateUseCase.Execute();
}
public LoadCruiserStateResult LoadCruiserState()
{
return loadCruiserStateUseCase.Execute();
}
public void PresentSaveCruiserStateResult(SaveCruiserStateResult result)
{
presentSaveCruiserStateResultUseCase.Execute(result);
}
public void PresentLoadCruiserStateResult(LoadCruiserStateResult result)
{
presentLoadCruiserStateResultUseCase.Execute(result);
}
public void RecordSaveServerRpcReceived()
{
validationLogger.Record(ValidationLogRecord.SaveServerRpcReceived(GetRole()));
}
public void RecordSaveClientRpcReceived(SaveCruiserStateResult result)
{
validationLogger.Record(ValidationLogRecord.SaveClientRpcReceived(GetRole(), result));
}
public void RecordLoadServerRpcReceived()
{
validationLogger.Record(ValidationLogRecord.LoadServerRpcReceived(GetRole()));
}
public void RecordLoadClientRpcReceived(LoadCruiserStateResult result)
{
validationLogger.Record(ValidationLogRecord.LoadClientRpcReceived(GetRole(), result));
}
public void HandleBaseGameEngineOilClientRpcEntered()
{
baseGameAppliedStateValidationHandler.EnterEngineOilClientRpc();
}
public void HandleBaseGameEngineOilClientRpcExited()
{
baseGameAppliedStateValidationHandler.ExitEngineOilClientRpc();
}
public void HandleBaseGameEngineOilLocalPreApply()
{
baseGameAppliedStateValidationHandler.HandleEngineOilLocalPreApply();
}
public void HandleBaseGameEngineOilLocalApplied()
{
baseGameAppliedStateValidationHandler.HandleEngineOilLocalApplied();
}
public void HandleBaseGameTurboClientRpcEntered()
{
baseGameAppliedStateValidationHandler.EnterTurboClientRpc();
}
public void HandleBaseGameTurboClientRpcExited()
{
baseGameAppliedStateValidationHandler.ExitTurboClientRpc();
}
public void HandleBaseGameTurboLocalPreApply()
{
baseGameAppliedStateValidationHandler.HandleTurboLocalPreApply();
}
public void HandleBaseGameTurboLocalApplied()
{
baseGameAppliedStateValidationHandler.HandleTurboLocalApplied();
}
public void HandleBaseGameShipMagnetLocalPreApply()
{
baseGameAppliedStateValidationHandler.HandleShipMagnetLocalPreApply();
}
public void HandleBaseGameShipMagnetLocalApplied()
{
baseGameAppliedStateValidationHandler.HandleShipMagnetLocalApplied();
}
public void HandleBaseGameShipMagnetClientRpcPreApply()
{
baseGameAppliedStateValidationHandler.HandleShipMagnetClientRpcPreApply();
}
public void HandleBaseGameShipMagnetClientRpcApplied()
{
baseGameAppliedStateValidationHandler.HandleShipMagnetClientRpcApplied();
}
private ValidationLogRole GetRole()
{
if (!gameInterop.IsHost())
{
return ValidationLogRole.Client;
}
return ValidationLogRole.Host;
}
}
public static class MyPluginInfo
{
public const string PLUGIN_GUID = "com.aoirint.CruiserJumpPractice";
public const string PLUGIN_NAME = "CruiserJumpPractice";
public const string PLUGIN_VERSION = "0.2.0";
}
}
namespace CruiserJumpPractice.Interop
{
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 = "[CJP_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("[CJP_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 CruiserJumpPractice.Interop.InputUtils
{
internal sealed class InputUtilsActions : LcInputActions
{
[InputAction(/*Could not decode attribute arguments.*/)]
public InputAction? LoadCruiserKey { get; set; }
[InputAction(/*Could not decode attribute arguments.*/)]
public InputAction? SaveCruiserKey { get; set; }
[InputAction(/*Could not decode attribute arguments.*/)]
public InputAction? ToggleMagnetKey { get; set; }
}
internal sealed class InputUtilsPracticeInput : IPracticeInput
{
private readonly InputUtilsActions inputActions;
public bool SaveCruiserTriggered
{
get
{
InputAction? saveCruiserKey = inputActions.SaveCruiserKey;
if (saveCruiserKey == null)
{
return false;
}
return saveCruiserKey.triggered;
}
}
public bool LoadCruiserTriggered
{
get
{
InputAction? loadCruiserKey = inputActions.LoadCruiserKey;
if (loadCruiserKey == null)
{
return false;
}
return loadCruiserKey.triggered;
}
}
public bool ToggleMagnetTriggered
{
get
{
InputAction? toggleMagnetKey = inputActions.ToggleMagnetKey;
if (toggleMagnetKey == null)
{
return false;
}
return toggleMagnetKey.triggered;
}
}
public InputUtilsPracticeInput(InputUtilsActions inputActions)
{
this.inputActions = inputActions;
}
}
}
namespace CruiserJumpPractice.Interop.Game
{
internal sealed class GameInterop : IGameInterop
{
private readonly NetworkAdapter networkInterop;
private readonly IValidationLogger validationLogger;
private readonly PlayerAdapter playerInterop;
private readonly HudAdapter hudInterop;
private readonly RpcSurrogateAdapter rpcSurrogateInterop;
private readonly CruiserAdapter cruiserInterop;
private readonly ShipMagnetAdapter shipMagnetInterop;
public GameInterop(IPluginLogger logger, IValidationLogger validationLogger)
{
this.validationLogger = validationLogger;
GameObjectAdapter gameObjects = new GameObjectAdapter(logger);
networkInterop = new NetworkAdapter(logger, gameObjects);
playerInterop = new PlayerAdapter(logger, gameObjects);
hudInterop = new HudAdapter(logger, gameObjects);
rpcSurrogateInterop = new RpcSurrogateAdapter(logger, gameObjects, validationLogger);
cruiserInterop = new CruiserAdapter(logger, gameObjects);
shipMagnetInterop = new ShipMagnetAdapter(logger, gameObjects);
}
public bool IsHost()
{
return networkInterop.IsHost();
}
public LocalPlayerBusyState GetLocalPlayerBusyState()
{
return playerInterop.GetLocalPlayerBusyState();
}
public void DisplayTip(HudTipMessage message)
{
validationLogger.Record(ValidationLogRecord.HudTip(GetRole(), message));
hudInterop.DisplayTip(message.HeaderText, message.BodyText);
}
public RpcSurrogateSpawnResult SpawnRpcSurrogate()
{
return rpcSurrogateInterop.SpawnRpcSurrogate();
}
public void RequestSaveCruiserState()
{
rpcSurrogateInterop.GetRpcSurrogateBehaviour().SaveCruiserStateServerRpc();
}
public void RequestLoadCruiserState()
{
rpcSurrogateInterop.GetRpcSurrogateBehaviour().LoadCruiserStateServerRpc();
}
public bool CruiserExists()
{
return (Object)(object)cruiserInterop.FindCruiser() != (Object)null;
}
public CruiserSnapshot? CaptureCruiser()
{
VehicleController val = cruiserInterop.FindCruiser();
if ((Object)(object)val == (Object)null)
{
return null;
}
return cruiserInterop.CaptureCruiser(val);
}
public int? GetCruiserCarHP()
{
VehicleController val = cruiserInterop.FindCruiser();
if ((Object)(object)val == (Object)null)
{
return null;
}
return CruiserAdapter.GetCarHP(val);
}
public int? GetCruiserTurboBoosts()
{
VehicleController val = cruiserInterop.FindCruiser();
if ((Object)(object)val == (Object)null)
{
return null;
}
return CruiserAdapter.GetTurboBoosts(val);
}
public CruiserRestoreObservation RestoreCruiser(CruiserSnapshot snapshot)
{
VehicleController val = cruiserInterop.FindCruiser();
if ((Object)(object)val == (Object)null)
{
throw new GameInteropException("No cruiser found.");
}
return cruiserInterop.RestoreCruiser(val, snapshot);
}
public bool IsCruiserMagnetedToShip()
{
VehicleController val = cruiserInterop.FindCruiser();
if ((Object)(object)val == (Object)null)
{
throw new GameInteropException("No cruiser found.");
}
return cruiserInterop.IsCruiserMagnetedToShip(val);
}
public bool IsShipMagnetOn()
{
return shipMagnetInterop.IsShipMagnetOn();
}
public void ToggleShipMagnet()
{
shipMagnetInterop.ToggleShipMagnet();
}
private ValidationLogRole GetRole()
{
if (!IsHost())
{
return ValidationLogRole.Client;
}
return ValidationLogRole.Host;
}
}
internal sealed class GameInteropException : Exception
{
public GameInteropException(string message)
: base(message)
{
}
}
}
namespace CruiserJumpPractice.Interop.Game.Patches
{
internal static class HarmonyPatchInstaller
{
private static readonly Harmony harmony = new Harmony("com.aoirint.CruiserJumpPractice");
public static void Install()
{
harmony.PatchAll(typeof(HarmonyPatchInstaller).Assembly);
}
}
[HarmonyPatch(typeof(HUDManager))]
internal class HUDManagerPatch
{
[HarmonyPatch("Awake")]
[HarmonyPostfix]
public static void AwakePostfix()
{
CruiserJumpPractice.Controller.HandleStartup();
}
[HarmonyPatch("Update")]
[HarmonyPostfix]
public static void UpdatePostfix()
{
CruiserJumpPractice.Controller.HandleFrame();
}
}
[HarmonyPatch(typeof(StartOfRound))]
internal static class StartOfRoundPatch
{
[HarmonyPatch("SetMagnetOn", new Type[] { typeof(bool) })]
[HarmonyPrefix]
public static void SetMagnetOnPrefix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameShipMagnetLocalPreApply();
});
}
[HarmonyPatch("SetMagnetOn", new Type[] { typeof(bool) })]
[HarmonyPostfix]
public static void SetMagnetOnPostfix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameShipMagnetLocalApplied();
});
}
[HarmonyPatch("SetMagnetOnClientRpc", new Type[] { typeof(bool) })]
[HarmonyPrefix]
public static void SetMagnetOnClientRpcPrefix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameShipMagnetClientRpcPreApply();
});
}
[HarmonyPatch("SetMagnetOnClientRpc", new Type[] { typeof(bool) })]
[HarmonyPostfix]
public static void SetMagnetOnClientRpcPostfix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameShipMagnetClientRpcApplied();
});
}
private static void TryNotifyAppliedStateValidation(Action notify)
{
try
{
notify();
}
catch
{
}
}
}
[HarmonyPatch(typeof(VehicleController))]
internal static class VehicleControllerPatch
{
[HarmonyPatch("AddEngineOilClientRpc", new Type[]
{
typeof(int),
typeof(int)
})]
[HarmonyPrefix]
public static void AddEngineOilClientRpcPrefix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameEngineOilClientRpcEntered();
});
}
[HarmonyPatch("AddEngineOilClientRpc", new Type[]
{
typeof(int),
typeof(int)
})]
[HarmonyFinalizer]
public static void AddEngineOilClientRpcFinalizer()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameEngineOilClientRpcExited();
});
}
[HarmonyPatch("AddEngineOilOnLocalClient", new Type[] { typeof(int) })]
[HarmonyPrefix]
public static void AddEngineOilOnLocalClientPrefix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameEngineOilLocalPreApply();
});
}
[HarmonyPatch("AddEngineOilOnLocalClient", new Type[] { typeof(int) })]
[HarmonyPostfix]
public static void AddEngineOilOnLocalClientPostfix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameEngineOilLocalApplied();
});
}
[HarmonyPatch("AddTurboBoostClientRpc", new Type[]
{
typeof(int),
typeof(int)
})]
[HarmonyPrefix]
public static void AddTurboBoostClientRpcPrefix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameTurboClientRpcEntered();
});
}
[HarmonyPatch("AddTurboBoostClientRpc", new Type[]
{
typeof(int),
typeof(int)
})]
[HarmonyFinalizer]
public static void AddTurboBoostClientRpcFinalizer()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameTurboClientRpcExited();
});
}
[HarmonyPatch("AddTurboBoostOnLocalClient", new Type[] { typeof(int) })]
[HarmonyPrefix]
public static void AddTurboBoostOnLocalClientPrefix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameTurboLocalPreApply();
});
}
[HarmonyPatch("AddTurboBoostOnLocalClient", new Type[] { typeof(int) })]
[HarmonyPostfix]
public static void AddTurboBoostOnLocalClientPostfix()
{
TryNotifyAppliedStateValidation(delegate
{
CruiserJumpPractice.Controller.HandleBaseGameTurboLocalApplied();
});
}
private static void TryNotifyAppliedStateValidation(Action notify)
{
try
{
notify();
}
catch
{
}
}
}
}
namespace CruiserJumpPractice.Interop.Game.Behaviours
{
internal class RpcSurrogateBehaviour : NetworkBehaviour
{
[ServerRpc(RequireOwnership = true)]
public void SaveCruiserStateServerRpc()
{
CruiserJumpPractice.Controller.RecordSaveServerRpcReceived();
SaveCruiserStateResult result = CruiserJumpPractice.Controller.SaveCruiserState();
SaveCruiserStateDoneClientRpc(result);
}
[ClientRpc]
public void SaveCruiserStateDoneClientRpc(SaveCruiserStateResult result)
{
CruiserJumpPractice.Controller.RecordSaveClientRpcReceived(result);
CruiserJumpPractice.Controller.PresentSaveCruiserStateResult(result);
}
[ServerRpc(RequireOwnership = true)]
public void LoadCruiserStateServerRpc()
{
CruiserJumpPractice.Controller.RecordLoadServerRpcReceived();
LoadCruiserStateResult result = CruiserJumpPractice.Controller.LoadCruiserState();
LoadCruiserStateDoneClientRpc(result);
}
[ClientRpc]
public void LoadCruiserStateDoneClientRpc(LoadCruiserStateResult result)
{
CruiserJumpPractice.Controller.RecordLoadClientRpcReceived(result);
CruiserJumpPractice.Controller.PresentLoadCruiserStateResult(result);
}
}
}
namespace CruiserJumpPractice.Interop.Game.Adapters
{
internal sealed class CruiserAdapter
{
private static readonly FieldInfo? turboBoostsField = typeof(VehicleController).GetField("turboBoosts", BindingFlags.Instance | BindingFlags.NonPublic);
private readonly IPluginLogger logger;
private readonly GameObjectAdapter gameObjects;
public CruiserAdapter(IPluginLogger logger, GameObjectAdapter gameObjects)
{
this.logger = logger;
this.gameObjects = gameObjects;
}
public VehicleController? FindCruiser()
{
try
{
VehicleController[] array = Object.FindObjectsOfType<VehicleController>();
if (array == null)
{
logger.LogError("Failed to find VehicleController objects.");
return null;
}
if (array.Length == 0)
{
logger.LogInfo("No VehicleController objects found.");
return null;
}
return array[0];
}
catch (Exception arg)
{
logger.LogError($"Exception while getting cruiser: {arg}");
throw new GameInteropException($"Exception while getting cruiser: {arg}");
}
}
public CruiserSnapshot CaptureCruiser(VehicleController cruiser)
{
//IL_0006: 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)
try
{
return new CruiserSnapshot(FromUnityVector3(((Component)cruiser).transform.position), FromUnityVector3(((Component)cruiser).transform.eulerAngles), cruiser.moveInputVector.x, cruiser.EngineRPM, cruiser.carHP, GetTurboBoosts(cruiser));
}
catch (Exception arg)
{
logger.LogError($"Exception while capturing cruiser state: {arg}");
throw new GameInteropException($"Exception while capturing cruiser state: {arg}");
}
}
public CruiserRestoreObservation RestoreCruiser(VehicleController cruiser, CruiserSnapshot snapshot)
{
//IL_0012: 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_004d: 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)
int localPlayerId = gameObjects.GetLocalPlayerId();
try
{
Vector3Value beforeCarPosition = FromUnityVector3(((Component)cruiser).transform.position);
int carHP = cruiser.carHP;
int turboBoosts = GetTurboBoosts(cruiser);
((Component)cruiser).transform.position = ToUnityVector3(snapshot.CarPosition);
((Component)cruiser).transform.eulerAngles = ToUnityVector3(snapshot.CarRotation);
cruiser.moveInputVector.x = snapshot.SteeringInput;
cruiser.EngineRPM = snapshot.EngineRPM;
cruiser.AddEngineOilOnLocalClient(snapshot.CarHP);
cruiser.AddEngineOilServerRpc(localPlayerId, snapshot.CarHP);
cruiser.AddTurboBoostOnLocalClient(snapshot.TurboBoosts);
cruiser.AddTurboBoostServerRpc(localPlayerId, snapshot.TurboBoosts);
return new CruiserRestoreObservation(snapshot.CarPosition, snapshot.CarRotation, beforeCarPosition, FromUnityVector3(((Component)cruiser).transform.position), snapshot.CarHP, carHP, cruiser.carHP, snapshot.TurboBoosts, turboBoosts, GetTurboBoosts(cruiser));
}
catch (Exception arg)
{
logger.LogError($"Exception while restoring cruiser state: {arg}");
throw new GameInteropException($"Exception while restoring cruiser state: {arg}");
}
}
public bool IsCruiserMagnetedToShip(VehicleController cruiser)
{
try
{
return cruiser.magnetedToShip;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting 'magnetedToShip': {arg}");
throw new GameInteropException($"Exception while getting 'magnetedToShip': {arg}");
}
}
internal static int GetCarHP(VehicleController cruiser)
{
return cruiser.carHP;
}
internal static int GetTurboBoosts(VehicleController cruiser)
{
if (turboBoostsField == null)
{
throw new GameInteropException("Failed to get 'turboBoosts' field from VehicleController.");
}
object value = turboBoostsField.GetValue(cruiser);
if (value is int)
{
return (int)value;
}
throw new GameInteropException("'turboBoosts' field is not of type int.");
}
private static Vector3Value FromUnityVector3(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 Vector3Value(value.x, value.y, value.z);
}
private static Vector3 ToUnityVector3(Vector3Value value)
{
//IL_0015: Unknown result type (might be due to invalid IL or missing references)
return new Vector3(value.X, value.Y, value.Z);
}
}
internal sealed class GameObjectAdapter
{
private readonly IPluginLogger logger;
public GameObjectAdapter(IPluginLogger logger)
{
this.logger = logger;
}
public HUDManager GetHUDManager()
{
try
{
HUDManager instance = HUDManager.Instance;
if ((Object)(object)instance == (Object)null)
{
throw new GameInteropException("HUDManager.Instance is null.");
}
return instance;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting HUDManager: {arg}");
throw new GameInteropException($"Exception while getting HUDManager: {arg}");
}
}
public NetworkManager GetNetworkManager()
{
try
{
NetworkManager singleton = NetworkManager.Singleton;
if ((Object)(object)singleton == (Object)null)
{
throw new GameInteropException("NetworkManager.Singleton is null.");
}
return singleton;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting NetworkManager: {arg}");
throw new GameInteropException($"Exception while getting NetworkManager: {arg}");
}
}
public PlayerControllerB GetLocalPlayer()
{
try
{
GameNetworkManager instance = GameNetworkManager.Instance;
if ((Object)(object)instance == (Object)null)
{
throw new GameInteropException("GameNetworkManager.Instance is null.");
}
PlayerControllerB localPlayerController = instance.localPlayerController;
if ((Object)(object)localPlayerController == (Object)null)
{
throw new GameInteropException("localPlayerController is null.");
}
return localPlayerController;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting local player: {arg}");
throw new GameInteropException($"Exception while getting local player: {arg}");
}
}
public int GetLocalPlayerId()
{
try
{
return (int)GetLocalPlayer().playerClientId;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting local player ID: {arg}");
throw new GameInteropException($"Exception while getting local player ID: {arg}");
}
}
public StartOfRound GetStartOfRound()
{
try
{
StartOfRound instance = StartOfRound.Instance;
if ((Object)(object)instance == (Object)null)
{
throw new GameInteropException("StartOfRound.Instance is null.");
}
return instance;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting StartOfRound: {arg}");
throw new GameInteropException($"Exception while getting StartOfRound: {arg}");
}
}
}
internal sealed class HudAdapter
{
private readonly IPluginLogger logger;
private readonly GameObjectAdapter gameObjects;
public HudAdapter(IPluginLogger logger, GameObjectAdapter gameObjects)
{
this.logger = logger;
this.gameObjects = gameObjects;
}
public void DisplayTip(string headerText, string bodyText)
{
HUDManager hUDManager = gameObjects.GetHUDManager();
try
{
hUDManager.DisplayTip(headerText, bodyText, false, false, "LC_Tip1");
}
catch (Exception arg)
{
logger.LogError($"Exception while displaying tip: {arg}");
throw new GameInteropException($"Exception while displaying tip: {arg}");
}
}
}
internal sealed class NetworkAdapter
{
private readonly IPluginLogger logger;
private readonly GameObjectAdapter gameObjects;
public NetworkAdapter(IPluginLogger logger, GameObjectAdapter gameObjects)
{
this.logger = logger;
this.gameObjects = gameObjects;
}
public bool IsHost()
{
try
{
return gameObjects.GetNetworkManager().IsHost;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting 'IsHost': {arg}");
throw new GameInteropException($"Exception while getting 'IsHost': {arg}");
}
}
}
internal sealed class PlayerAdapter
{
private readonly IPluginLogger logger;
private readonly GameObjectAdapter gameObjects;
public PlayerAdapter(IPluginLogger logger, GameObjectAdapter gameObjects)
{
this.logger = logger;
this.gameObjects = gameObjects;
}
public LocalPlayerBusyState GetLocalPlayerBusyState()
{
PlayerControllerB localPlayer = gameObjects.GetLocalPlayer();
try
{
QuickMenuManager quickMenuManager = localPlayer.quickMenuManager;
if ((Object)(object)quickMenuManager == (Object)null)
{
throw new GameInteropException("quickMenuManager is null.");
}
return new LocalPlayerBusyState(quickMenuManager.isMenuOpen, localPlayer.inTerminalMenu, localPlayer.isTypingChat);
}
catch (Exception arg)
{
logger.LogError($"Exception while getting local player status: {arg}");
throw new GameInteropException($"Exception while getting local player status: {arg}");
}
}
}
internal sealed class RpcSurrogateAdapter
{
private readonly IPluginLogger logger;
private readonly GameObjectAdapter gameObjects;
private readonly IValidationLogger validationLogger;
private RpcSurrogateBehaviour? cachedRpcSurrogateBehaviour;
public RpcSurrogateAdapter(IPluginLogger logger, GameObjectAdapter gameObjects, IValidationLogger validationLogger)
{
this.logger = logger;
this.gameObjects = gameObjects;
this.validationLogger = validationLogger;
}
public RpcSurrogateSpawnResult SpawnRpcSurrogate()
{
try
{
GameObject gameObject = ((Component)gameObjects.GetHUDManager()).gameObject;
if ((Object)(object)gameObject == (Object)null)
{
logger.LogError("HUDManager.gameObject is null.");
return RpcSurrogateSpawnResult.Missing;
}
RpcSurrogateBehaviour component = gameObject.GetComponent<RpcSurrogateBehaviour>();
if ((Object)(object)component != (Object)null)
{
cachedRpcSurrogateBehaviour = component;
logger.LogDebug("RPC surrogate already exists on HUDManager.");
return RpcSurrogateSpawnResult.Reused;
}
cachedRpcSurrogateBehaviour = gameObject.AddComponent<RpcSurrogateBehaviour>();
logger.LogInfo("Spawned RPC surrogate on HUDManager.");
return RpcSurrogateSpawnResult.Added;
}
catch (Exception arg)
{
logger.LogError($"Exception while spawning RPC surrogate: {arg}");
return RpcSurrogateSpawnResult.Error;
}
}
public RpcSurrogateBehaviour GetRpcSurrogateBehaviour()
{
if ((Object)(object)cachedRpcSurrogateBehaviour != (Object)null)
{
RecordResolved(ValidationLogRpcSurrogateResolveSource.Cache, ValidationLogRpcSurrogateResolveResult.Success);
return cachedRpcSurrogateBehaviour;
}
try
{
RpcSurrogateBehaviour component = ((Component)gameObjects.GetHUDManager()).GetComponent<RpcSurrogateBehaviour>();
if ((Object)(object)component == (Object)null)
{
throw new GameInteropException("RpcSurrogateBehaviour component not found on HUDManager instance.");
}
cachedRpcSurrogateBehaviour = component;
RecordResolved(ValidationLogRpcSurrogateResolveSource.Lookup, ValidationLogRpcSurrogateResolveResult.Success);
return component;
}
catch (Exception arg)
{
RecordResolved(ValidationLogRpcSurrogateResolveSource.Lookup, ValidationLogRpcSurrogateResolveResult.Error);
logger.LogError($"Exception while getting RpcSurrogateBehaviour: {arg}");
throw new GameInteropException($"Exception while getting RpcSurrogateBehaviour: {arg}");
}
}
private void RecordResolved(ValidationLogRpcSurrogateResolveSource source, ValidationLogRpcSurrogateResolveResult result)
{
validationLogger.Record(ValidationLogRecord.RpcSurrogateResolved(source, result));
}
}
internal sealed class ShipMagnetAdapter
{
private readonly IPluginLogger logger;
private readonly GameObjectAdapter gameObjects;
public ShipMagnetAdapter(IPluginLogger logger, GameObjectAdapter gameObjects)
{
this.logger = logger;
this.gameObjects = gameObjects;
}
public bool IsShipMagnetOn()
{
try
{
return gameObjects.GetStartOfRound().magnetOn;
}
catch (Exception arg)
{
logger.LogError($"Exception while getting 'magnetOn': {arg}");
throw new GameInteropException($"Exception while getting 'magnetOn': {arg}");
}
}
public void ToggleShipMagnet()
{
try
{
AnimatedObjectTrigger magnetLever = gameObjects.GetStartOfRound().magnetLever;
if ((Object)(object)magnetLever == (Object)null)
{
throw new GameInteropException("StartOfRound.magnetLever is null.");
}
magnetLever.TriggerAnimation(gameObjects.GetLocalPlayer());
}
catch (Exception arg)
{
logger.LogError($"Exception while toggling magnet: {arg}");
throw new GameInteropException($"Exception while toggling magnet: {arg}");
}
}
}
}
namespace CruiserJumpPractice.Core.Validation
{
internal sealed class DisabledValidationLogger : IValidationLogger
{
public static DisabledValidationLogger Instance { get; } = new DisabledValidationLogger();
private DisabledValidationLogger()
{
}
public void Record(ValidationLogRecord record)
{
}
}
internal enum ValidationLogRole
{
Host,
Client
}
internal enum ValidationLogInputAction
{
Save,
Load,
ToggleMagnet
}
internal enum ValidationLogRpcSurrogateResolveSource
{
Cache,
Lookup
}
internal enum ValidationLogRpcSurrogateResolveResult
{
Success,
Error
}
internal enum ValidationLogBaseGameApplySource
{
LocalApply,
ClientRpcApply,
Unknown
}
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)
{
return new ValidationLogRecord("plugin_loaded", new Dictionary<string, object>
{
["version"] = version,
["validation_logging"] = validationLogging
});
}
public static ValidationLogRecord StateStoreCreated()
{
return new ValidationLogRecord("state_store_created");
}
public static ValidationLogRecord ControllerCreated()
{
return new ValidationLogRecord("controller_created");
}
public static ValidationLogRecord HudStartup(RpcSurrogateSpawnResult surrogateResult)
{
return new ValidationLogRecord("hud_startup", new Dictionary<string, object> { ["surrogate"] = ToSurrogateResultToken(surrogateResult) });
}
public static ValidationLogRecord InputTriggered(ValidationLogInputAction action, ValidationLogRole role)
{
return new ValidationLogRecord("input_triggered", new Dictionary<string, object>
{
["action"] = ToValidationActionToken(action),
["role"] = ToValidationRoleToken(role),
["busy"] = false
});
}
public static ValidationLogRecord InputSuppressed(ValidationLogInputAction action, ValidationLogRole role, LocalPlayerBusyState busyState)
{
return new ValidationLogRecord("input_suppressed", new Dictionary<string, object>
{
["action"] = ToValidationActionToken(action),
["role"] = ToValidationRoleToken(role),
["reason"] = busyState.GetBusyReasonToken() ?? "unknown",
["menu"] = busyState.IsMenuOpen,
["terminal"] = busyState.IsInTerminal,
["chat"] = busyState.IsTypingChat
});
}
public static ValidationLogRecord RequestSaveResult(ValidationLogRole role, RequestSaveCruiserStateResult result)
{
return Result("request_save_result", role, ToValidationResultToken(result));
}
public static ValidationLogRecord RequestLoadResult(ValidationLogRole role, RequestLoadCruiserStateResult result)
{
return Result("request_load_result", role, ToValidationResultToken(result));
}
public static ValidationLogRecord ToggleMagnetResultEvent(ValidationLogRole role, ToggleMagnetResult result)
{
return Result("toggle_magnet_result", role, ToValidationResultToken(result));
}
public static ValidationLogRecord MagnetToggle(MagnetToggleObservation observation)
{
return new ValidationLogRecord("magnet_toggle", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(ValidationLogRole.Host),
["before"] = ToValidationStateToken(observation.BeforeState),
["expected_after"] = ToValidationStateToken(observation.ExpectedAfterState),
["observed_after"] = ToValidationStateToken(observation.ObservedAfterState)
});
}
public static ValidationLogRecord SaveServerRpcReceived(ValidationLogRole role)
{
return Role("save_server_rpc_received", role);
}
public static ValidationLogRecord SaveClientRpcReceived(ValidationLogRole role, SaveCruiserStateResult result)
{
return Result("save_client_rpc_received", role, ToValidationResultToken(result));
}
public static ValidationLogRecord LoadServerRpcReceived(ValidationLogRole role)
{
return Role("load_server_rpc_received", role);
}
public static ValidationLogRecord LoadClientRpcReceived(ValidationLogRole role, LoadCruiserStateResult result)
{
return Result("load_client_rpc_received", role, ToValidationResultToken(result));
}
public static ValidationLogRecord SaveNoCruiserFound()
{
return new ValidationLogRecord("save_result", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(ValidationLogRole.Host),
["result"] = ToValidationResultToken(SaveCruiserStateResult.NoCruiserFound),
["cruiser_found"] = false
});
}
public static ValidationLogRecord SaveUnexpectedState()
{
return Result("save_result", ValidationLogRole.Host, "unexpected_state");
}
public static ValidationLogRecord SaveSuccess(CruiserSnapshot cruiserState)
{
return new ValidationLogRecord("save_result", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(ValidationLogRole.Host),
["result"] = ToValidationResultToken(SaveCruiserStateResult.Success),
["cruiser_found"] = true,
["pos"] = Vector3(cruiserState.CarPosition, 1),
["rot"] = Vector3(cruiserState.CarRotation, 1),
["hp"] = cruiserState.CarHP,
["turbo"] = cruiserState.TurboBoosts,
["steering"] = Number(cruiserState.SteeringInput, 2),
["rpm"] = Number(cruiserState.EngineRPM, 2)
});
}
public static ValidationLogRecord LoadNoCruiserFound(bool savedState)
{
return LoadResult(ToValidationResultToken(LoadCruiserStateResult.NoCruiserFound), cruiserFound: false, savedState, "unknown");
}
public static ValidationLogRecord LoadNoSavedState()
{
return LoadResult(ToValidationResultToken(LoadCruiserStateResult.NoSavedState), cruiserFound: true, savedState: false, "unknown");
}
public static ValidationLogRecord LoadMagnetedToShip()
{
return LoadResult(ToValidationResultToken(LoadCruiserStateResult.MagnetedToShip), cruiserFound: true, savedState: true, true);
}
public static ValidationLogRecord LoadSuccess()
{
return LoadResult(ToValidationResultToken(LoadCruiserStateResult.Success), cruiserFound: true, savedState: true, false);
}
public static ValidationLogRecord LoadUnexpectedState()
{
return Result("load_result", ValidationLogRole.Host, "unexpected_state");
}
public static ValidationLogRecord RestoreApplied(CruiserRestoreObservation observation)
{
return new ValidationLogRecord("restore_applied", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(ValidationLogRole.Host),
["saved_pos"] = Vector3(observation.SavedCarPosition, 1),
["saved_rot"] = Vector3(observation.SavedCarRotation, 1),
["before_pos"] = Vector3(observation.BeforeCarPosition, 1),
["after_pos"] = Vector3(observation.AfterCarPosition, 1),
["saved_hp"] = observation.SavedCarHP,
["before_hp"] = observation.BeforeCarHP,
["after_hp"] = observation.AfterCarHP,
["saved_turbo"] = observation.SavedTurboBoosts,
["before_turbo"] = observation.BeforeTurboBoosts,
["after_turbo"] = observation.AfterTurboBoosts
});
}
public static ValidationLogRecord BaseGameEngineOilApplied(ValidationLogRole role, int? beforeCarHP, int? afterCarHP, ValidationLogBaseGameApplySource source)
{
return new ValidationLogRecord("base_game_engine_oil_applied", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(role),
["before_hp"] = beforeCarHP,
["after_hp"] = afterCarHP,
["source"] = ToBaseGameApplySourceToken(source)
});
}
public static ValidationLogRecord BaseGameTurboApplied(ValidationLogRole role, int? beforeTurbo, int? afterTurbo, ValidationLogBaseGameApplySource source)
{
return new ValidationLogRecord("base_game_turbo_applied", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(role),
["before_turbo"] = beforeTurbo,
["after_turbo"] = afterTurbo,
["source"] = ToBaseGameApplySourceToken(source)
});
}
public static ValidationLogRecord BaseGameShipMagnetApplied(ValidationLogRole role, bool? before, bool after, ValidationLogBaseGameApplySource source)
{
return new ValidationLogRecord("base_game_ship_magnet_applied", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(role),
["before"] = before,
["after"] = after,
["source"] = ToBaseGameApplySourceToken(source)
});
}
public static ValidationLogRecord HudTip(ValidationLogRole role, HudTipMessage message)
{
return new ValidationLogRecord("hud_tip", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(role),
["message"] = message.Token
});
}
public static ValidationLogRecord RpcSurrogateResolved(ValidationLogRpcSurrogateResolveSource source, ValidationLogRpcSurrogateResolveResult result)
{
return new ValidationLogRecord("rpc_surrogate_resolved", new Dictionary<string, object>
{
["source"] = ToRpcSurrogateResolveSourceToken(source),
["result"] = ToRpcSurrogateResolveResultToken(result)
});
}
private static ValidationLogRecord LoadResult(string result, bool cruiserFound, bool savedState, object? magneted)
{
return new ValidationLogRecord("load_result", new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(ValidationLogRole.Host),
["result"] = result,
["cruiser_found"] = cruiserFound,
["saved_state"] = savedState,
["magneted"] = magneted
});
}
private static ValidationLogRecord Result(string eventName, ValidationLogRole role, string result)
{
return new ValidationLogRecord(eventName, new Dictionary<string, object>
{
["role"] = ToValidationRoleToken(role),
["result"] = result
});
}
private static ValidationLogRecord Role(string eventName, ValidationLogRole role)
{
return new ValidationLogRecord(eventName, new Dictionary<string, object> { ["role"] = ToValidationRoleToken(role) });
}
private static object? Number(float value, int decimalPlaces)
{
if (float.IsNaN(value) || float.IsInfinity(value))
{
return null;
}
return Math.Round(value, decimalPlaces, MidpointRounding.AwayFromZero);
}
private static object?[] Vector3(Vector3Value value, int decimalPlaces)
{
return new object[3]
{
Number(value.X, decimalPlaces),
Number(value.Y, decimalPlaces),
Number(value.Z, decimalPlaces)
};
}
private static string ToSurrogateResultToken(RpcSurrogateSpawnResult result)
{
return result switch
{
RpcSurrogateSpawnResult.Added => "added",
RpcSurrogateSpawnResult.Reused => "reused",
RpcSurrogateSpawnResult.Missing => "missing",
RpcSurrogateSpawnResult.Error => "error",
_ => "error",
};
}
private static string ToValidationRoleToken(ValidationLogRole role)
{
return role switch
{
ValidationLogRole.Host => "host",
ValidationLogRole.Client => "client",
_ => "client",
};
}
private static string ToValidationActionToken(ValidationLogInputAction action)
{
return action switch
{
ValidationLogInputAction.Save => "save",
ValidationLogInputAction.Load => "load",
ValidationLogInputAction.ToggleMagnet => "toggle_magnet",
_ => "toggle_magnet",
};
}
private static string ToRpcSurrogateResolveSourceToken(ValidationLogRpcSurrogateResolveSource source)
{
return source switch
{
ValidationLogRpcSurrogateResolveSource.Cache => "cache",
ValidationLogRpcSurrogateResolveSource.Lookup => "lookup",
_ => "lookup",
};
}
private static string ToRpcSurrogateResolveResultToken(ValidationLogRpcSurrogateResolveResult result)
{
return result switch
{
ValidationLogRpcSurrogateResolveResult.Success => "success",
ValidationLogRpcSurrogateResolveResult.Error => "error",
_ => "error",
};
}
private static string ToBaseGameApplySourceToken(ValidationLogBaseGameApplySource source)
{
return source switch
{
ValidationLogBaseGameApplySource.LocalApply => "local_apply",
ValidationLogBaseGameApplySource.ClientRpcApply => "client_rpc_apply",
ValidationLogBaseGameApplySource.Unknown => "unknown",
_ => "unknown",
};
}
private static string ToValidationResultToken(SaveCruiserStateResult result)
{
return result switch
{
SaveCruiserStateResult.Success => "success",
SaveCruiserStateResult.NoCruiserFound => "no_cruiser_found",
SaveCruiserStateResult.UnexpectedState => "unexpected_state",
_ => "unexpected_state",
};
}
private static string ToValidationResultToken(LoadCruiserStateResult result)
{
return result switch
{
LoadCruiserStateResult.Success => "success",
LoadCruiserStateResult.NoCruiserFound => "no_cruiser_found",
LoadCruiserStateResult.NoSavedState => "no_saved_state",
LoadCruiserStateResult.MagnetedToShip => "magneted_to_ship",
LoadCruiserStateResult.UnexpectedState => "unexpected_state",
_ => "unexpected_state",
};
}
private static string ToValidationResultToken(RequestSaveCruiserStateResult result)
{
return result switch
{
RequestSaveCruiserStateResult.Success => "success",
RequestSaveCruiserStateResult.HostOnly => "host_only",
_ => "host_only",
};
}
private static string ToValidationResultToken(RequestLoadCruiserStateResult result)
{
return result switch
{
RequestLoadCruiserStateResult.Success => "success",
RequestLoadCruiserStateResult.HostOnly => "host_only",
_ => "host_only",
};
}
private static string ToValidationResultToken(ToggleMagnetResult result)
{
return result switch
{
ToggleMagnetResult.MagnetOn => "magnet_on",
ToggleMagnetResult.MagnetOff => "magnet_off",
ToggleMagnetResult.HostOnly => "host_only",
_ => "host_only",
};
}
private static string ToValidationStateToken(MagnetState state)
{
return state switch
{
MagnetState.On => "on",
MagnetState.Off => "off",
MagnetState.Unknown => "unknown",
_ => "unknown",
};
}
}
}
namespace CruiserJumpPractice.Core.UseCases
{
internal enum RequestSaveCruiserStateResult
{
Success,
HostOnly
}
internal enum RequestLoadCruiserStateResult
{
Success,
HostOnly
}
internal enum SaveCruiserStateResult
{
Success,
NoCruiserFound,
UnexpectedState
}
internal enum LoadCruiserStateResult
{
Success,
NoCruiserFound,
NoSavedState,
MagnetedToShip,
UnexpectedState
}
internal enum ToggleMagnetResult
{
HostOnly,
MagnetOn,
MagnetOff
}
}
namespace CruiserJumpPractice.Core.UseCases.Server
{
internal sealed class LoadCruiserStateUseCase
{
private readonly IGameInterop gameInterop;
private readonly CruiserStateStore cruiserStateStore;
private readonly IPluginLogger logger;
private readonly IValidationLogger validationLogger;
public LoadCruiserStateUseCase(IGameInterop gameInterop, CruiserStateStore cruiserStateStore, IPluginLogger logger, IValidationLogger validationLogger)
{
this.gameInterop = gameInterop;
this.cruiserStateStore = cruiserStateStore;
this.logger = logger;
this.validationLogger = validationLogger;
}
public LoadCruiserStateResult Execute()
{
try
{
if (!gameInterop.CruiserExists())
{
logger.LogInfo("No cruiser found.");
validationLogger.Record(ValidationLogRecord.LoadNoCruiserFound(cruiserStateStore.SavedCruiserState != null));
return LoadCruiserStateResult.NoCruiserFound;
}
CruiserSnapshot savedCruiserState = cruiserStateStore.SavedCruiserState;
if (savedCruiserState == null)
{
logger.LogInfo("No saved cruiser state found.");
validationLogger.Record(ValidationLogRecord.LoadNoSavedState());
return LoadCruiserStateResult.NoSavedState;
}
if (gameInterop.IsCruiserMagnetedToShip())
{
logger.LogInfo("Cruiser is currently magneted to the ship. Cannot load state.");
validationLogger.Record(ValidationLogRecord.LoadMagnetedToShip());
return LoadCruiserStateResult.MagnetedToShip;
}
CruiserRestoreObservation observation = gameInterop.RestoreCruiser(savedCruiserState);
RecordRestoreApplied(observation);
validationLogger.Record(ValidationLogRecord.LoadSuccess());
return LoadCruiserStateResult.Success;
}
catch (Exception arg)
{
logger.LogError($"Exception while loading cruiser state: {arg}");
validationLogger.Record(ValidationLogRecord.LoadUnexpectedState());
return LoadCruiserStateResult.UnexpectedState;
}
}
private void RecordRestoreApplied(CruiserRestoreObservation observation)
{
validationLogger.Record(ValidationLogRecord.RestoreApplied(observation));
}
}
internal sealed class SaveCruiserStateUseCase
{
private readonly IGameInterop gameInterop;
private readonly CruiserStateStore cruiserStateStore;
private readonly IPluginLogger logger;
private readonly IValidationLogger validationLogger;
public SaveCruiserStateUseCase(IGameInterop gameInterop, CruiserStateStore cruiserStateStore, IPluginLogger logger, IValidationLogger validationLogger)
{
this.gameInterop = gameInterop;
this.cruiserStateStore = cruiserStateStore;
this.logger = logger;
this.validationLogger = validationLogger;
}
public SaveCruiserStateResult Execute()
{
try
{
CruiserSnapshot cruiserSnapshot = gameInterop.CaptureCruiser();
if (cruiserSnapshot == null)
{
logger.LogInfo("No cruiser found.");
validationLogger.Record(ValidationLogRecord.SaveNoCruiserFound());
return SaveCruiserStateResult.NoCruiserFound;
}
cruiserStateStore.SavedCruiserState = cruiserSnapshot;
RecordSaveSuccess(cruiserSnapshot);
return SaveCruiserStateResult.Success;
}
catch (Exception arg)
{
logger.LogError($"Exception while saving cruiser state: {arg}");
validationLogger.Record(ValidationLogRecord.SaveUnexpectedState());
return SaveCruiserStateResult.UnexpectedState;
}
}
private void RecordSaveSuccess(CruiserSnapshot cruiserState)
{
validationLogger.Record(ValidationLogRecord.SaveSuccess(cruiserState));
}
}
}
namespace CruiserJumpPractice.Core.UseCases.Client
{
internal enum MagnetState
{
Unknown,
On,
Off
}
internal sealed class MagnetToggleObservation
{
public MagnetState BeforeState { get; }
public MagnetState ExpectedAfterState { get; }
public MagnetState ObservedAfterState { get; }
private MagnetToggleObservation(MagnetState beforeState, MagnetState expectedAfterState, MagnetState observedAfterState)
{
BeforeState = beforeState;
ExpectedAfterState = expectedAfterState;
ObservedAfterState = observedAfterState;
}
public static MagnetToggleObservation FromBeforeState(bool beforeIsOn)
{
int beforeState = (beforeIsOn ? 1 : 2);
MagnetState expectedAfterState = ((!beforeIsOn) ? MagnetState.On : MagnetState.Off);
return new MagnetToggleObservation((MagnetState)beforeState, expectedAfterState, MagnetState.Unknown);
}
}
internal sealed class PresentLoadCruiserStateResultUseCase
{
private readonly IGameInterop gameInterop;
private readonly IPluginLogger logger;
public PresentLoadCruiserStateResultUseCase(IGameInterop gameInterop, IPluginLogger logger)
{
this.gameInterop = gameInterop;
this.logger = logger;
}
public void Execute(LoadCruiserStateResult result)
{
switch (result)
{
case LoadCruiserStateResult.Success:
DisplayTip(HudTipMessage.LoadSuccess);
break;
case LoadCruiserStateResult.NoCruiserFound:
DisplayTip(HudTipMessage.LoadNoCruiser);
break;
case LoadCruiserStateResult.NoSavedState:
DisplayTip(HudTipMessage.LoadNoSavedState);
break;
case LoadCruiserStateResult.MagnetedToShip:
DisplayTip(HudTipMessage.LoadMagnetedToShip);
break;
default:
logger.LogError($"Unknown LoadCruiserStateResult: {result}");
break;
}
}
private void DisplayTip(HudTipMessage message)
{
gameInterop.DisplayTip(message);
}
}
internal sealed class PresentSaveCruiserStateResultUseCase
{
private readonly IGameInterop gameInterop;
private readonly IPluginLogger logger;
public PresentSaveCruiserStateResultUseCase(IGameInterop gameInterop, IPluginLogger logger)
{
this.gameInterop = gameInterop;
this.logger = logger;
}
public void Execute(SaveCruiserStateResult result)
{
switch (result)
{
case SaveCruiserStateResult.Success:
DisplayTip(HudTipMessage.SaveSuccess);
break;
case SaveCruiserStateResult.NoCruiserFound:
DisplayTip(HudTipMessage.SaveNoCruiser);
break;
default:
logger.LogError($"Unknown SaveCruiserStateResult: {result}");
break;
}
}
private void DisplayTip(HudTipMessage message)
{
gameInterop.DisplayTip(message);
}
}
internal sealed class RequestLoadCruiserStateUseCase
{
private readonly IGameInterop gameInterop;
private readonly IValidationLogger validationLogger;
public RequestLoadCruiserStateUseCase(IGameInterop gameInterop, IValidationLogger validationLogger)
{
this.gameInterop = gameInterop;
this.validationLogger = validationLogger;
}
public RequestLoadCruiserStateResult Execute()
{
if (!gameInterop.IsHost())
{
gameInterop.DisplayTip(HudTipMessage.LoadHostOnly);
RecordResult(ValidationLogRole.Client, RequestLoadCruiserStateResult.HostOnly);
return RequestLoadCruiserStateResult.HostOnly;
}
RecordResult(ValidationLogRole.Host, RequestLoadCruiserStateResult.Success);
gameInterop.RequestLoadCruiserState();
return RequestLoadCruiserStateResult.Success;
}
private void RecordResult(ValidationLogRole role, RequestLoadCruiserStateResult result)
{
validationLogger.Record(ValidationLogRecord.RequestLoadResult(role, result));
}
}
internal sealed class RequestSaveCruiserStateUseCase
{
private readonly IGameInterop gameInterop;
private readonly IValidationLogger validationLogger;
public RequestSaveCruiserStateUseCase(IGameInterop gameInterop, IValidationLogger validationLogger)
{
this.gameInterop = gameInterop;
this.validationLogger = validationLogger;
}
public RequestSaveCruiserStateResult Execute()
{
if (!gameInterop.IsHost())
{
gameInterop.DisplayTip(HudTipMessage.SaveHostOnly);
RecordResult(ValidationLogRole.Client, RequestSaveCruiserStateResult.HostOnly);
return RequestSaveCruiserStateResult.HostOnly;
}
RecordResult(ValidationLogRole.Host, RequestSaveCruiserStateResult.Success);
gameInterop.RequestSaveCruiserState();
return RequestSaveCruiserStateResult.Success;
}
private void RecordResult(ValidationLogRole role, RequestSaveCruiserStateResult result)
{
validationLogger.Record(ValidationLogRecord.RequestSaveResult(role, result));
}
}
internal sealed class ToggleMagnetUseCase
{
private readonly IGameInterop gameInterop;
private readonly IValidationLogger validationLogger;
public ToggleMagnetUseCase(IGameInterop gameInterop, IValidationLogger validationLogger)
{
this.gameInterop = gameInterop;
this.validationLogger = validationLogger;
}
public ToggleMagnetResult Execute()
{
if (!gameInterop.IsHost())
{
gameInterop.DisplayTip(HudTipMessage.MagnetHostOnly);
RecordResult(ValidationLogRole.Client, ToggleMagnetResult.HostOnly);
return ToggleMagnetResult.HostOnly;
}
MagnetToggleObservation magnetToggleObservation = MagnetToggleObservation.FromBeforeState(gameInterop.IsShipMagnetOn());
gameInterop.ToggleShipMagnet();
validationLogger.Record(ValidationLogRecord.MagnetToggle(magnetToggleObservation));
ToggleMagnetResult toggleMagnetResult = ((magnetToggleObservation.ExpectedAfterState == MagnetState.On) ? ToggleMagnetResult.MagnetOn : ToggleMagnetResult.MagnetOff);
validationLogger.Record(ValidationLogRecord.ToggleMagnetResultEvent(ValidationLogRole.Host, toggleMagnetResult));
HudTipMessage message = ((toggleMagnetResult == ToggleMagnetResult.MagnetOn) ? HudTipMessage.MagnetOn : HudTipMessage.MagnetOff);
gameInterop.DisplayTip(message);
return toggleMagnetResult;
}
private void RecordResult(ValidationLogRole role, ToggleMagnetResult result)
{
validationLogger.Record(ValidationLogRecord.ToggleMagnetResultEvent(role, result));
}
}
}
namespace CruiserJumpPractice.Core.State
{
internal sealed class BaseGameAppliedStateValidationStore
{
private int engineOilClientRpcDepth;
private int turboClientRpcDepth;
private int? preEngineOilLocalApplyCarHP;
private int? preTurboLocalApplyBoosts;
private bool? preMagnetLocalApplyState;
private bool? preMagnetClientRpcApplyState;
public bool IsEngineOilClientRpcApplyActive => engineOilClientRpcDepth > 0;
public bool IsTurboClientRpcApplyActive => turboClientRpcDepth > 0;
public int? PreEngineOilLocalApplyCarHP => preEngineOilLocalApplyCarHP;
public int? PreTurboLocalApplyBoosts => preTurboLocalApplyBoosts;
public bool? PreMagnetLocalApplyState => preMagnetLocalApplyState;
public bool? PreMagnetClientRpcApplyState => preMagnetClientRpcApplyState;
public void SetPreEngineOilLocalApplyCarHP(int? value)
{
preEngineOilLocalApplyCarHP = value;
}
public void SetPreTurboLocalApplyBoosts(int? value)
{
preTurboLocalApplyBoosts = value;
}
public void SetPreMagnetLocalApplyState(bool? value)
{
preMagnetLocalApplyState = value;
}
public void SetPreMagnetClientRpcApplyState(bool? value)
{
preMagnetClientRpcApplyState = value;
}
public void EnterEngineOilClientRpc()
{
engineOilClientRpcDepth++;
}
public void ExitEngineOilClientRpc()
{
if (engineOilClientRpcDepth > 0)
{
engineOilClientRpcDepth--;
}
}
public void EnterTurboClientRpc()
{
turboClientRpcDepth++;
}
public void ExitTurboClientRpc()
{
if (turboClientRpcDepth > 0)
{
turboClientRpcDepth--;
}
}
}
internal sealed class CruiserStateStore
{
public CruiserSnapshot? SavedCruiserState { get; set; }
}
internal readonly struct LocalPlayerBusyState
{
public const string MenuReasonToken = "menu";
public const string TerminalReasonToken = "terminal";
public const string ChatReasonToken = "chat";
public const string MultipleReasonToken = "multiple";
public bool IsMenuOpen { get; }
public bool IsInTerminal { get; }
public bool IsTypingChat { get; }
public bool IsBusy
{
get
{
if (!IsMenuOpen && !IsInTerminal)
{
return IsTypingChat;
}
return true;
}
}
public LocalPlayerBusyState(bool isMenuOpen, bool isInTerminal, bool isTypingChat)
{
IsMenuOpen = isMenuOpen;
IsInTerminal = isInTerminal;
IsTypingChat = isTypingChat;
}
public string? GetBusyReasonToken()
{
if (0 + (IsMenuOpen ? 1 : 0) + (IsInTerminal ? 1 : 0) + (IsTypingChat ? 1 : 0) > 1)
{
return "multiple";
}
if (IsMenuOpen)
{
return "menu";
}
if (IsInTerminal)
{
return "terminal";
}
if (IsTypingChat)
{
return "chat";
}
return null;
}
}
}
namespace CruiserJumpPractice.Core.Snapshots
{
internal sealed class CruiserRestoreObservation
{
public Vector3Value SavedCarPosition { get; }
public Vector3Value SavedCarRotation { get; }
public Vector3Value BeforeCarPosition { get; }
public Vector3Value AfterCarPosition { get; }
public int SavedCarHP { get; }
public int BeforeCarHP { get; }
public int AfterCarHP { get; }
public int SavedTurboBoosts { get; }
public int BeforeTurboBoosts { get; }
public int AfterTurboBoosts { get; }
public CruiserRestoreObservation(Vector3Value savedCarPosition, Vector3Value savedCarRotation, Vector3Value beforeCarPosition, Vector3Value afterCarPosition, int savedCarHP, int beforeCarHP, int afterCarHP, int savedTurboBoosts, int beforeTurboBoosts, int afterTurboBoosts)
{
SavedCarPosition = savedCarPosition;
SavedCarRotation = savedCarRotation;
BeforeCarPosition = beforeCarPosition;
AfterCarPosition = afterCarPosition;
SavedCarHP = savedCarHP;
BeforeCarHP = beforeCarHP;
AfterCarHP = afterCarHP;
SavedTurboBoosts = savedTurboBoosts;
BeforeTurboBoosts = beforeTurboBoosts;
AfterTurboBoosts = afterTurboBoosts;
}
}
internal sealed class CruiserSnapshot
{
public Vector3Value CarPosition { get; }
public Vector3Value CarRotation { get; }
public float SteeringInput { get; }
public float EngineRPM { get; }
public int CarHP { get; }
public int TurboBoosts { get; }
public CruiserSnapshot(Vector3Value carPosition, Vector3Value carRotation, float steeringInput, float engineRPM, int carHP, int turboBoosts)
{
CarPosition = carPosition;
CarRotation = carRotation;
SteeringInput = steeringInput;
EngineRPM = engineRPM;
CarHP = carHP;
TurboBoosts = turboBoosts;
}
}
internal readonly struct Vector3Value
{
public float X { get; }
public float Y { get; }
public float Z { get; }
public Vector3Value(float x, float y, float z)
{
X = x;
Y = y;
Z = z;
}
}
}
namespace CruiserJumpPractice.Core.Presentation
{
internal sealed class HudTipMessage
{
private const string DefaultHeaderText = "CruiserJumpPractice";
public static readonly HudTipMessage SaveSuccess = new HudTipMessage("save_success", "CruiserJumpPractice", "Cruiser state saved.");
public static readonly HudTipMessage SaveNoCruiser = new HudTipMessage("save_no_cruiser", "CruiserJumpPractice", "No cruiser found to save.");
public static readonly HudTipMessage SaveHostOnly = new HudTipMessage("save_host_only", "CruiserJumpPractice", "Only the host can save the cruiser state.");
public static readonly HudTipMessage LoadSuccess = new HudTipMessage("load_success", "CruiserJumpPractice", "Cruiser state loaded.");
public static readonly HudTipMessage LoadNoCruiser = new HudTipMessage("load_no_cruiser", "CruiserJumpPractice", "No cruiser found to load.");
public static readonly HudTipMessage LoadNoSavedState = new HudTipMessage("load_no_saved_state", "CruiserJumpPractice", "No saved cruiser state to load.");
public static readonly HudTipMessage LoadMagnetedToShip = new HudTipMessage("load_magneted_to_ship", "CruiserJumpPractice", "Cannot load cruiser state while magneted to ship.");
public static readonly HudTipMessage LoadHostOnly = new HudTipMessage("load_host_only", "CruiserJumpPractice", "Only the host can load the cruiser state.");
public static readonly HudTipMessage MagnetHostOnly = new HudTipMessage("magnet_host_only", "CruiserJumpPractice", "Only the host can toggle the magnet.");
public static readonly HudTipMessage MagnetOn = new HudTipMessage("magnet_on", "CruiserJumpPractice", "Magnet is now ON.");
public static readonly HudTipMessage MagnetOff = new HudTipMessage("magnet_off", "CruiserJumpPractice", "Magnet is now OFF.");
public string Token { get; }
public string HeaderText { get; }
public string BodyText { get; }
private HudTipMessage(string token, string headerText, string bodyText)
{
Token = token;
HeaderText = headerText;
BodyText = bodyText;
}
}
}
namespace CruiserJumpPractice.Core.Ports
{
internal interface IGameInterop
{
bool IsHost();
LocalPlayerBusyState GetLocalPlayerBusyState();
void DisplayTip(HudTipMessage message);
RpcSurrogateSpawnResult SpawnRpcSurrogate();
void RequestSaveCruiserState();
void RequestLoadCruiserState();
bool CruiserExists();
CruiserSnapshot? CaptureCruiser();
int? GetCruiserCarHP();
int? GetCruiserTurboBoosts();
CruiserRestoreObservation RestoreCruiser(CruiserSnapshot snapshot);
bool IsCruiserMagnetedToShip();
bool IsShipMagnetOn();
void ToggleShipMagnet();
}
internal enum RpcSurrogateSpawnResult
{
Added,
Reused,
Missing,
Error
}
internal interface IPluginLogger
{
void LogDebug(string message);
void LogInfo(string message);
void LogError(string message);
}
internal interface IPracticeInput
{
bool SaveCruiserTriggered { get; }
bool LoadCruiserTriggered { get; }
bool ToggleMagnetTriggered { get; }
}
internal interface IValidationLogger
{
void Record(ValidationLogRecord record);
}
}
namespace CruiserJumpPractice.Core.Handlers
{
internal sealed class BaseGameAppliedStateValidationHandler
{
private readonly IGameInterop gameInterop;
private readonly IValidationLogger validationLogger;
private readonly BaseGameAppliedStateValidationStore stateStore;
public BaseGameAppliedStateValidationHandler(IGameInterop gameInterop, IValidationLogger validationLogger, BaseGameAppliedStateValidationStore stateStore)
{
this.gameInterop = gameInterop;
this.validationLogger = validationLogger;
this.stateStore = stateStore;
}
public void EnterEngineOilClientRpc()
{
stateStore.EnterEngineOilClientRpc();
}
public void ExitEngineOilClientRpc()
{
stateStore.ExitEngineOilClientRpc();
}
public void HandleEngineOilLocalPreApply()
{
stateStore.SetPreEngineOilLocalApplyCarHP(gameInterop.GetCruiserCarHP());
}
public void HandleEngineOilLocalApplied()
{
if (stateStore.IsEngineOilClientRpcApplyActive)
{
validationLogger.Record(ValidationLogRecord.BaseGameEngineOilApplied(GetRole(), stateStore.PreEngineOilLocalApplyCarHP, gameInterop.GetCruiserCarHP(), ValidationLogBaseGameApplySource.ClientRpcApply));
}
}
public void EnterTurboClientRpc()
{
stateStore.EnterTurboClientRpc();
}
public void ExitTurboClientRpc()
{
stateStore.ExitTurboClientRpc();
}
public void HandleTurboLocalPreApply()
{
stateStore.SetPreTurboLocalApplyBoosts(gameInterop.GetCruiserTurboBoosts());
}
public void HandleTurboLocalApplied()
{
if (stateStore.IsTurboClientRpcApplyActive)
{
validationLogger.Record(ValidationLogRecord.BaseGameTurboApplied(GetRole(), stateStore.PreTurboLocalApplyBoosts, gameInterop.GetCruiserTurboBoosts(), ValidationLogBaseGameApplySource.ClientRpcApply));
}
}
public void HandleShipMagnetLocalPreApply()
{
stateStore.SetPreMagnetLocalApplyState(gameInterop.IsShipMagnetOn());
}
public void HandleShipMagnetLocalApplied()
{
HandleShipMagnetApplied(stateStore.PreMagnetLocalApplyState, gameInterop.IsShipMagnetOn(), ValidationLogBaseGameApplySource.LocalApply);
}
public void HandleShipMagnetClientRpcPreApply()
{
stateStore.SetPreMagnetClientRpcApplyState(gameInterop.IsShipMagnetOn());
}
public void HandleShipMagnetClientRpcApplied()
{
HandleShipMagnetApplied(stateStore.PreMagnetClientRpcApplyState, gameInterop.IsShipMagnetOn(), ValidationLogBaseGameApplySource.ClientRpcApply);
}
private void HandleShipMagnetApplied(bool? before, bool after, ValidationLogBaseGameApplySource source)
{
validationLogger.Record(ValidationLogRecord.BaseGameShipMagnetApplied(GetRole(), before, after, source));
}
private ValidationLogRole GetRole()
{
if (!gameInterop.IsHost())
{
return ValidationLogRole.Client;
}
return ValidationLogRole.Host;
}
}
internal sealed class FrameHandler
{
private readonly IGameInterop gameInterop;
private readonly IPracticeInput practiceInput;
private readonly IValidationLogger validationLogger;
private readonly RequestSaveCruiserStateUseCase requestSaveCruiserStateUseCase;
private readonly RequestLoadCruiserStateUseCase requestLoadCruiserStateUseCase;
private readonly ToggleMagnetUseCase toggleMagnetUseCase;
public FrameHandler(IGameInterop gameInterop, IPracticeInput practiceInput, IValidationLogger validationLogger, RequestSaveCruiserStateUseCase requestSaveCruiserStateUseCase, RequestLoadCruiserStateUseCase requestLoadCruiserStateUseCase, ToggleMagnetUseCase toggleMagnetUseCase)
{
this.gameInterop = gameInterop;
this.practiceInput = practiceInput;
this.validationLogger = validationLogger;
this.requestSaveCruiserStateUseCase = requestSaveCruiserStateUseCase;
this.requestLoadCruiserStateUseCase = requestLoadCruiserStateUseCase;
this.toggleMagnetUseCase = toggleMagnetUseCase;
}
public void HandleFrame()
{
bool saveCruiserTriggered = practiceInput.SaveCruiserTriggered;
bool loadCruiserTriggered = practiceInput.LoadCruiserTriggered;
bool toggleMagnetTriggered = practiceInput.ToggleMagnetTriggered;
if (!saveCruiserTriggered && !loadCruiserTriggered && !toggleMagnetTriggered)
{
return;
}
LocalPlayerBusyState localPlayerBusyState = gameInterop.GetLocalPlayerBusyState();
if (localPlayerBusyState.IsBusy)
{
RecordSuppressedInput(saveCruiserTriggered, loadCruiserTriggered, toggleMagnetTriggered, localPlayerBusyState);
return;
}
if (saveCruiserTriggered)
{
RecordTriggeredInput(ValidationLogInputAction.Save);
requestSaveCruiserStateUseCase.Execute();
}
if (loadCruiserTriggered)
{
RecordTriggeredInput(ValidationLogInputAction.Load);
requestLoadCruiserStateUseCase.Execute();
}
if (toggleMagnetTriggered)
{
RecordTriggeredInput(ValidationLogInputAction.ToggleMagnet);
toggleMagnetUseCase.Execute();
}
}
private void RecordSuppressedInput(bool saveTriggered, bool loadTriggered, bool toggleMagnetTriggered, LocalPlayerBusyState busyState)
{
if (saveTriggered)
{
RecordSuppressedInput(ValidationLogInputAction.Save, busyState);
}
if (loadTriggered)
{
RecordSuppressedInput(ValidationLogInputAction.Load, busyState);
}
if (toggleMagnetTriggered)
{
RecordSuppressedInput(ValidationLogInputAction.ToggleMagnet, busyState);
}
}
private void RecordTriggeredInput(ValidationLogInputAction action)
{
validationLogger.Record(ValidationLogRecord.InputTriggered(action, GetRole()));
}
private void RecordSuppressedInput(ValidationLogInputAction action, LocalPlayerBusyState busyState)
{
validationLogger.Record(ValidationLogRecord.InputSuppressed(action, GetRole(), busyState));
}
private ValidationLogRole GetRole()
{
if (!gameInterop.IsHost())
{
return ValidationLogRole.Client;
}
return ValidationLogRole.Host;
}
}
internal sealed class StartupHandler
{
private readonly IGameInterop gameInterop;
private readonly IValidationLogger validationLogger;
public StartupHandler(IGameInterop gameInterop, IValidationLogger validationLogger)
{
this.gameInterop = gameInterop;
this.validationLogger = validationLogger;
}
public void HandleStartup()
{
RpcSurrogateSpawnResult surrogateResult = gameInterop.SpawnRpcSurrogate();
validationLogger.Record(ValidationLogRecord.HudStartup(surrogateResult));
}
}
}