feat(equipment): persist mech loadouts and recover ejection safe case loot

This commit is contained in:
2026-11-12 18:02:26 +08:00
parent 0857e291d5
commit e1d38c5a6d
23 changed files with 1371 additions and 204 deletions
+25 -17
View File
@@ -1,11 +1,12 @@
using System;
using System.Linq;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.UIElements;
namespace Ironspore
{
public sealed class GameHud : MonoBehaviour
public sealed partial class GameHud : MonoBehaviour
{
GameSession game;
VisualElement root;
@@ -37,6 +38,9 @@ namespace Ironspore
root.styleSheets.Add(game.uiStyle);
root.pickingMode = PickingMode.Ignore;
root.style.unityFontDefinition = FontDefinition.FromSDFFont(game.localization.uiFont);
root.RegisterCallback<PointerMoveEvent>(DragMove, TrickleDown.TrickleDown);
root.RegisterCallback<PointerUpEvent>(DragEnd, TrickleDown.TrickleDown);
root.RegisterCallback<PointerCancelEvent>(_ => ResetDrag(), TrickleDown.TrickleDown);
root.RegisterCallback<KeyDownEvent>(HandleLanguageKey, TrickleDown.TrickleDown);
GameLocalization.Changed += RefreshLocale;
}
@@ -106,6 +110,7 @@ namespace Ironspore
}
void Clear()
{
ResetDrag();
root.Clear();
mission = prompt = status = notice = telemetry = null;
armor = energy = cargo = null;
@@ -225,7 +230,7 @@ namespace Ironspore
{
int s = slot;
var p = game.Part(s, game.InitialEquipment[s]);
Button(content, L("ui.home.part", L("ui.slot." + s), p.LocalizedName, p.mass, p.power), () => { game.InitialEquipment[s] = (game.InitialEquipment[s] + 1) % game.PartCount(s); ShowHangar(); }, "loadout");
Button(content, L("ui.home.part", L("ui.slot." + s), p.LocalizedName, p.mass, p.power), () => { game.SelectedInstances[s] = null; game.InitialEquipment[s] = (game.InitialEquipment[s] + 1) % game.PartCount(s); ShowHangar(); }, "loadout");
}
float mass = 0, power = 0;
for (int i = 0; i < 4; i++)
@@ -249,12 +254,27 @@ namespace Ironspore
var save = game.Store.Data;
Text(content, L("ui.home.inventory"), "section");
Text(content, L("ui.home.stock", save.alloy, save.cells, save.blueprints.Count, save.extractions, save.completed), "budget");
foreach (var item in save.warehouse)
{
int slot = InventoryGrid.Slot(item.itemId);
Button(content, L("ui.home.chooseItem", item.Name, item.uid.Substring(0, Math.Min(6, item.uid.Length))), () =>
{
game.SelectedInstances[slot] = item.uid;
game.InitialEquipment[slot] = InventoryGrid.Index(slot, item.itemId);
ShowHangar();
}, game.SelectedInstances[slot] == item.uid ? "loadout selected" : "loadout");
}
foreach (string blueprint in save.blueprints)
{
string id = blueprint;
bool unlocked = save.unlocked.Contains(id);
var b = Button(content, L(unlocked ? "ui.home.researched" : "ui.home.research", GameLocalization.ItemName(id)), () => game.Unlock(id), "loadout");
b.SetEnabled(!unlocked);
var recipe = InventoryGrid.Recipe(id);
if (unlocked && recipe.item != null)
Button(content, L("ui.home.manufacture", L("equipment." + recipe.item + ".name"), recipe.alloy, recipe.cells),
() => { if (!game.Store.Manufacture(id)) game.Notify("notice.saveFailed"); ShowHangar(); }, "loadout")
.SetEnabled(!save.run.active && save.alloy >= recipe.alloy && save.cells >= recipe.cells);
}
hangarNotice = Text(content, game.Notice, "warning");
RefreshHangarNotice();
@@ -314,19 +334,6 @@ namespace Ironspore
Text(panel, body, "body");
return panel;
}
public void ShowCargo()
{
var panel = Overlay(L("ui.cargo.title"), L(game.Run.podUsed ? "ui.cargo.used" : "ui.cargo.help"));
view = View.Cargo;
foreach (var stack in game.Run.cargo)
{
string uid = stack.uid;
var b = Button(panel, L("ui.cargo.stack", game.SelectedCargo.Contains(uid) ? L("ui.cargo.selected") : "[ ]", stack.Name, stack.count), () => game.ToggleCargo(uid), "loadout");
b.SetEnabled(!game.Run.podUsed);
}
Text(panel, L("ui.cargo.capacity", game.Run.cargo.Count, game.SelectedCargo.Count), "budget");
Button(panel, L("ui.return"), game.CloseMenu, "primary");
}
public void ShowPart(int slot, int index)
{
index = Mathf.Clamp(index, 0, game.PartCount(slot) - 1);
@@ -337,7 +344,7 @@ namespace Ironspore
var next = game.Part(slot, index);
var panel = Overlay(L("ui.refit.title"), L("ui.refit.help"));
view = View.Part;
Text(panel, L("ui.refit.equipped", old.LocalizedName, old.mass, old.power), "body");
Text(panel, L("ui.refit.equipped", old?.LocalizedName ?? "—", old?.mass ?? 0, old?.power ?? 0), "body");
Text(panel, L("ui.refit.recovered", next.LocalizedName, next.mass, next.power), "budget");
Button(panel, L("ui.refit.install"), () => game.Install(slot, partIndex), "primary");
Button(panel, L("ui.refit.keep"), game.CloseMenu);
@@ -378,6 +385,7 @@ namespace Ironspore
ActivateFocusedLanguage();
}
}
if (view == View.Cargo && selectedUid != null && Keyboard.current?.rKey.wasPressedThisFrame == true) RotateSelection();
if (!game.InField || !game.Pilot)
return;
var run = game.Run;
@@ -409,7 +417,7 @@ namespace Ironspore
if (notice != null)
notice.text = game.Notice;
if (telemetry != null)
telemetry.text = L("ui.hud.telemetry", game.chassis[run.chassis].LocalizedName, L(p.FirstPerson ? "ui.hud.cockpit" : "ui.hud.shoulder"), game.Part(0, run.equipment[0]).LocalizedName, run.cargo.Count, game.World.ExtractionRemaining >= 0 ? game.World.TransportReady ? L("ui.hud.transportReady") : L("ui.hud.transportEta", Mathf.CeilToInt(game.World.ExtractionRemaining)) : L("ui.hud.extractionAvailable"));
telemetry.text = L("ui.hud.telemetry", game.chassis[run.chassis].LocalizedName, L(p.FirstPerson ? "ui.hud.cockpit" : "ui.hud.shoulder"), game.Part(0, run.equipment[0])?.LocalizedName ?? "—", run.cargo.Sum(InventoryGrid.Area), game.World.ExtractionRemaining >= 0 ? game.World.TransportReady ? L("ui.hud.transportReady") : L("ui.hud.transportEta", Mathf.CeilToInt(game.World.ExtractionRemaining)) : L("ui.hud.extractionAvailable"));
if (cross != null)
cross.style.opacity = p.RecentHit ? 1 : .65f;
}