using System; using System.Collections.Generic; using System.IO; using UnityEngine; using UnityEngine.InputSystem; namespace Ironspore { public sealed class GameSession : MonoBehaviour { public static GameSession Active { get; private set; } public ChassisDefinition[] chassis; public EquipmentDefinition[] equipment; public GameObject mechModel; public InputActionAsset inputTemplate; public UnityEngine.UIElements.StyleSheet uiStyle; public UnityEngine.UIElements.PanelSettings uiPanel; public LocalizationCatalog localization; public bool startInRange; public ProfileStore Store { get; private set; } ProfileStore mainStore; public RunRecord Run => Store.Data.run; public MechPilot Pilot { get; private set; } public WarzoneDirector World { get; private set; } public GameHud Hud { get; private set; } public InputActionAsset Input { get; private set; } public SettingsService Settings { get; private set; } public readonly List SelectedCargo = new List(); public bool MenuOpen { get; private set; } = true; public bool Paused { get; private set; } public bool Trial { get; private set; } public int SelectedChassis = 1; public int[] InitialEquipment = { 0, 0, 0, 0 }; LocalizedMessage noticeMessage; public string Notice => noticeMessage?.ToString() ?? ""; public float CargoProgress { get; private set; } public bool InField { get; private set; } LocalizedMessage reportMessage = GameLocalization.Message("report.ready"); public string Report => reportMessage.ToString(); float noticeUntil; float autosaveTimer; int cargoDamageFrame = -1; bool ending; void Awake() { Active = this; GameLocalization.Initialize(localization); Application.targetFrameRate = 120; mainStore = new ProfileStore(Path.Combine(GameDataPaths.DirectoryPath, "profile.json")); Store = mainStore; Input = Instantiate(inputTemplate); var defaults = SettingsService.RuntimeDefaults(); Settings = new SettingsService(new SettingsStore(Path.Combine(GameDataPaths.DirectoryPath, "settings.json"), defaults), Input, defaults); Input.Enable(); World = gameObject.AddComponent(); Hud = gameObject.AddComponent(); Hud.Initialize(this); } void Start() { World.BuildHangar(); Hud.ShowHangar(); SetMenu(true); if (startInRange) StartTrial(); } public EquipmentDefinition Part(int slot, int index) { var list = Array.FindAll(equipment, x => x.slot == slot); return list[Mathf.Clamp(index, 0, list.Length - 1)]; } public int PartCount(int slot) => Array.FindAll(equipment, x => x.slot == slot).Length; public bool Fits(int frame, int[] parts) { float mass = 0, power = 0; for (int i = 0; i < 4; i++) { var p = Part(i, parts[i]); mass += p.mass; power += p.power; } return mass <= chassis[frame].massCapacity && power <= chassis[frame].powerCapacity; } public bool StartingPartsUnlocked() { var unlocked = mainStore.Data.unlocked; return (InitialEquipment[0] != 1 || unlocked.Contains("blueprint_coil_capacitor")) && (InitialEquipment[2] != 1 || unlocked.Contains("blueprint_boost_efficient")) && (InitialEquipment[3] != 1 || unlocked.Contains("blueprint_reactive_guard")); } public void BeginSortie() { if (Store.Data.run.active) { Notify("notice.unfinished"); return; } if (!Fits(SelectedChassis, InitialEquipment)) { Notify("notice.budget"); return; } if (!StartingPartsUnlocked()) { Notify("notice.researchFirst"); return; } Trial = false; Store = mainStore; if (!Store.Begin(SelectedChassis, InitialEquipment, chassis[SelectedChassis].armor, Environment.TickCount)) { NotifySaveFailure(); return; } EnterField(); } public void StartTrial() { Trial = true; Store = new ProfileStore(Path.Combine(GameDataPaths.DirectoryPath, "range.json")); Store.Begin(SelectedChassis, InitialEquipment, chassis[SelectedChassis].armor, 1739); EnterField(); } public void Resume() { Trial = false; Store = mainStore; if (Run.active) EnterField(); } void EnterField() { ending = false; InField = true; SelectedCargo.Clear(); CargoProgress = 0; World.BuildWarzone(); Pilot = World.Player; SetMenu(false); Hud.ShowField(); if (Trial) Notify("notice.range", Settings.Bindings.Display("View"), Settings.Bindings.Display("Interact")); else Notify("notice.enter"); } public void SetMenu(bool open) { MenuOpen = open; Cursor.lockState = open ? CursorLockMode.None : CursorLockMode.Locked; Cursor.visible = open; if (Pilot) Pilot.RequireFreshTrigger(); if (open) CargoProgress = 0; } public void CloseMenu() { Paused = false; Time.timeScale = 1; if (InField) { SetMenu(false); Hud.ShowField(); } } public void Notify(string key, params object[] args) { noticeMessage = GameLocalization.Message(key, args); noticeUntil = Time.unscaledTime + 5; Hud.RefreshHangarNotice(); } void NotifySaveFailure(string fallbackKey = "notice.saveFailed") { if (!string.IsNullOrEmpty(Store.Error)) Debug.LogError("Save failed: " + Store.Error); Notify(string.IsNullOrEmpty(Store.Error) ? fallbackKey : "notice.saveFailed"); } public InputAction Action(string name) => Input.FindAction("Pilot/" + name, true); void Update() { if (Time.unscaledTime > noticeUntil) noticeMessage = null; if (Hud && (Hud.SettingsOpen || Hud.MenuInputConsumedThisFrame)) return; if (!InField || !Pilot || ending) return; if (Action("Pause").WasPressedThisFrame()) { if (MenuOpen) CloseMenu(); else { Paused = true; Time.timeScale = 0; SetMenu(true); Hud.ShowPause(); } } if (Paused) return; Run.elapsed += Time.deltaTime; if (Action("Inventory").WasPressedThisFrame()) { if (MenuOpen) CloseMenu(); else { SetMenu(true); Hud.ShowCargo(); } } if (!MenuOpen && Action("Interact").WasPressedThisFrame() && World.Focused) World.Focused.Activate(); TickCargo(); if (Pilot.Health <= 0) { EndSortie(true); return; } autosaveTimer += Time.deltaTime; if (autosaveTimer >= 10) { SnapshotVitals(); Store.Commit(s => true); autosaveTimer = 0; } } void TickCargo() { bool uninterrupted = Pilot.LastDamageFrame == cargoDamageFrame; cargoDamageFrame = Pilot.LastDamageFrame; bool preparing = !MenuOpen && !Run.podUsed && SelectedCargo.Count > 0 && Action("Cargo").IsPressed() && Pilot.Grounded && Action("Move").ReadValue().sqrMagnitude < .01f && Pilot.HorizontalSpeed < .2f && uninterrupted; if (!preparing) { CargoProgress = 0; return; } CargoProgress += Time.deltaTime; if (CargoProgress < 3) return; SnapshotVitals(); if (Store.SendCargo(SelectedCargo)) { World.LaunchPod(Pilot.transform.position); Notify("notice.podSecured"); GameAudio.Cue(720, .3f); SelectedCargo.Clear(); } else NotifySaveFailure("notice.cargoUnavailable"); CargoProgress = 0; } public void ToggleCargo(string uid) { if (SelectedCargo.Contains(uid)) SelectedCargo.Remove(uid); else if (SelectedCargo.Count < 2) SelectedCargo.Add(uid); else Notify("notice.twoSlots"); Hud.ShowCargo(); } public void SnapshotVitals() { if (!Pilot) return; Run.armor = Mathf.Max(0, Pilot.Health); Run.energy = Pilot.Energy; } public void Search(WorldInteractable target) { if (!Store.AddCargo(target.itemId, target.amount, target.key)) { NotifySaveFailure("notice.holdFull"); return; } target.collected = true; target.RefreshLabel(); Notify("notice.recovered", GameLocalization.Message("item." + target.itemId), target.amount); GameAudio.Cue(520, .12f); if (target.offersPart) { SetMenu(true); Hud.ShowPart(target.partSlot, target.partIndex); } } public void Install(int slot, int index) { var next = (int[])Run.equipment.Clone(); next[slot] = index; if (!Fits(Run.chassis, next)) { Notify("notice.partBudget"); return; } Store.Commit(s => { s.run.equipment = next; return true; }); Pilot.ApplyLoadout(); CloseMenu(); Notify("notice.refit", GameLocalization.Message("equipment." + Part(slot, index).id + ".name")); } public void CompleteObjective() { if (Run.objectiveDone) return; Store.Commit(s => { s.run.objectiveDone = true; return true; }); Notify("notice.routeOpen"); GameAudio.Cue(850, .2f); } public void CompleteTerminal() { if (!Store.CompleteRoute()) return; Notify("notice.complete"); GameAudio.Cue(1100, .4f); } public void OpenRoutes() { if (!Run.objectiveDone) { Notify("notice.objectiveFirst"); return; } if (Trial) { Notify("notice.rangeComplete"); return; } if (Run.stage >= 4) { Notify("notice.extract"); return; } SetMenu(true); Hud.ShowRoutes(); } public void NextZone(int option) { SnapshotVitals(); bool ok = Store.Commit(s => { if (s.run.stage == 1) s.run.branch = option; s.run.stage++; s.run.biome = option; s.run.objectiveDone = false; s.run.upgrades++; return true; }); if (ok) EnterField(); else NotifySaveFailure(); } public void Unlock(string id) { bool ok = Store.Commit(s => { if (!s.blueprints.Contains(id) || s.unlocked.Contains(id) || s.alloy < 2 || s.cells < 1) return false; s.alloy -= 2; s.cells--; s.unlocked.Add(id); return true; }); Notify(ok ? "notice.researched" : "notice.researchCost"); Hud.ShowHangar(); } public void EndSortie(bool destroyed) { if (ending) return; SnapshotVitals(); bool complete = Run.routeCompleted; int lost = Run.cargo.Count; if (!Store.Finish(destroyed)) { NotifySaveFailure(); return; } ending = true; reportMessage = GameLocalization.Message(Trial ? "report.range" : destroyed ? "report.destroyed" : complete ? "report.complete" : "report.extracted", lost); InField = false; Trial = false; Store = mainStore; Pilot = null; World.BuildHangar(); SetMenu(true); Hud.ShowHangar(); } void OnApplicationFocus(bool focus) { if (!focus && Hud && Hud.SettingsOpen) { Hud.CancelSettingsInput(); return; } if (!focus && InField && Hud) { Paused = true; Time.timeScale = 0; SetMenu(true); Hud.ShowPause(); } } void OnApplicationQuit() { if (InField && Pilot) { SnapshotVitals(); Store.Commit(s => true); } } void OnDestroy() { Settings?.Dispose(); if (Input) Destroy(Input); Time.timeScale = 1; if (Active == this) Active = null; } } }