Files
unity/Assets/Game/Scripts/Runs/GameSession.cs
T
RCiBot eb819c6688 feat: improve map lighting and expand the playable training range
Add real-time lighting and shadows, training courses and targets, and animated salvage crates. Include bilingual assets and curated verification results.
2026-11-12 18:03:04 +08:00

446 lines
14 KiB
C#

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 WorldLightingProfile lightingProfile;
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 readonly List<string> SelectedCargo = new List<string>();
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 = 60;
mainStore = new ProfileStore(Path.Combine(Application.persistentDataPath, "Ironspore", "profile.json"));
Store = mainStore;
Input = Instantiate(inputTemplate);
Input.Enable();
World = gameObject.AddComponent<WarzoneDirector>();
Hud = gameObject.AddComponent<GameHud>();
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(Application.persistentDataPath, "Ironspore", "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();
Notify(Trial ? "notice.range" : "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 (!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<Vector2>().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 && InField && Hud)
{
Paused = true;
Time.timeScale = 0;
SetMenu(true);
Hud.ShowPause();
}
}
void OnApplicationQuit()
{
if (InField && Pilot)
{
SnapshotVitals();
Store.Commit(s => true);
}
}
void OnDestroy()
{
if (Input)
Destroy(Input);
Time.timeScale = 1;
if (Active == this)
Active = null;
}
}
}