feat(equipment): persist mech loadouts and recover ejection safe case loot
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UIElements;
|
||||
|
||||
namespace Ironspore
|
||||
{
|
||||
public sealed partial class GameHud
|
||||
{
|
||||
string selectedUid;
|
||||
Hold selectedHold;
|
||||
int selectedSlot = -1;
|
||||
bool rotated;
|
||||
int quantity;
|
||||
const int CellWidth = 103, CellHeight = 55;
|
||||
|
||||
sealed class GridCell
|
||||
{
|
||||
public Hold hold;
|
||||
public int x, y;
|
||||
public CargoStack occupant;
|
||||
}
|
||||
|
||||
CargoStack dragItem;
|
||||
Hold dragHold;
|
||||
int dragSlot, dragPointer;
|
||||
Vector2 dragStart;
|
||||
VisualElement dragPreview;
|
||||
|
||||
CargoStack Selection => selectedSlot >= 0 ? game.Run.installed.Find(i => i.uid == selectedUid) :
|
||||
Contents(selectedHold).Find(i => i.uid == selectedUid);
|
||||
|
||||
List<CargoStack> Contents(Hold hold) => hold == Hold.Cargo ? game.Run.cargo : game.Run.safeCase;
|
||||
|
||||
void SelectInventoryItem(CargoStack item, Hold hold, int slot = -1)
|
||||
{
|
||||
selectedUid = item?.uid;
|
||||
selectedSlot = slot;
|
||||
selectedHold = hold;
|
||||
rotated = item?.rotated ?? false;
|
||||
quantity = item?.count ?? 0;
|
||||
ShowCargo();
|
||||
}
|
||||
|
||||
void RotateSelection()
|
||||
{
|
||||
rotated = !rotated;
|
||||
if (dragPreview == null) ShowCargo();
|
||||
else SizePreview();
|
||||
}
|
||||
|
||||
public void ShowCargo()
|
||||
{
|
||||
var panel = Overlay(L("ui.cargo.title"), L(game.Run.podUsed ? "ui.cargo.used" : "ui.cargo.help"));
|
||||
view = View.Cargo;
|
||||
var installed = Element(panel, "row");
|
||||
foreach (var part in game.Run.installed)
|
||||
{
|
||||
int slot = InventoryGrid.Slot(part.itemId);
|
||||
var button = Button(installed, part.Name, () => SelectInventoryItem(part, Hold.Cargo, slot),
|
||||
part.uid == selectedUid ? "loadout selected" : "loadout");
|
||||
EnableDrag(button, part, Hold.Cargo, slot);
|
||||
}
|
||||
var grids = Element(panel, "row");
|
||||
DrawHold(grids, Hold.Cargo, InventoryGrid.CargoSize);
|
||||
DrawHold(grids, Hold.SafeCase, InventoryGrid.CaseSize);
|
||||
Text(panel, L("ui.cargo.capacity", game.Run.cargo.Sum(InventoryGrid.Area),
|
||||
game.Run.cargo.Where(i => game.SelectedCargo.Contains(i.uid)).Sum(InventoryGrid.Area)), "budget");
|
||||
var item = Selection;
|
||||
if (item != null)
|
||||
{
|
||||
InventoryGrid.Size(item.itemId, rotated, out int w, out int h);
|
||||
Text(panel, L("ui.cargo.selection", item.Name, w, h, quantity), "body");
|
||||
var actions = Element(panel, "row");
|
||||
Button(actions, L("ui.cargo.rotate"), RotateSelection);
|
||||
if (item.count > 1)
|
||||
{
|
||||
Button(actions, "−", () => { quantity--; ShowCargo(); }).SetEnabled(quantity > 1);
|
||||
Button(actions, "+", () => { quantity++; ShowCargo(); }).SetEnabled(quantity < item.count);
|
||||
}
|
||||
if (selectedSlot < 0)
|
||||
{
|
||||
if (selectedHold == Hold.Cargo && !game.Run.podUsed)
|
||||
Button(actions, L(game.SelectedCargo.Contains(item.uid) ? "ui.cargo.unmark" : "ui.cargo.mark"), () => game.ToggleCargo(item.uid));
|
||||
if (InventoryGrid.Slot(item.itemId) >= 0)
|
||||
Button(actions, L("ui.cargo.install"), () =>
|
||||
{
|
||||
if (game.InstallItem(item, selectedHold)) selectedUid = null;
|
||||
ShowCargo();
|
||||
});
|
||||
}
|
||||
Button(actions, L("ui.cargo.cancel"), () => SelectInventoryItem(null, Hold.Cargo));
|
||||
}
|
||||
Text(panel, game.Notice, "warning");
|
||||
Button(panel, L("ui.return"), game.CloseMenu, "primary");
|
||||
}
|
||||
|
||||
void DrawHold(VisualElement parent, Hold hold, int size)
|
||||
{
|
||||
var column = Element(parent, "inventory-column");
|
||||
Text(column, L(hold == Hold.Cargo ? "ui.cargo.hold" : "ui.cargo.case"), "section");
|
||||
var grid = new VisualElement();
|
||||
column.Add(grid);
|
||||
grid.style.width = size * CellWidth;
|
||||
grid.style.height = size * CellHeight;
|
||||
var items = Contents(hold);
|
||||
for (int y = 0; y < size; y++)
|
||||
for (int x = 0; x < size; x++)
|
||||
{
|
||||
int cx = x, cy = y;
|
||||
var occupant = items.Find(i =>
|
||||
{
|
||||
InventoryGrid.Size(i.itemId, i.rotated, out int w, out int h);
|
||||
return cx >= i.x && cy >= i.y && cx < i.x + w && cy < i.y + h;
|
||||
});
|
||||
var cell = Button(grid, "", () => InventoryClick(hold, cx, cy, occupant), "inventory-cell");
|
||||
cell.name = hold + "-" + x + "-" + y;
|
||||
cell.tooltip = occupant?.Name ?? "";
|
||||
cell.userData = new GridCell { hold = hold, x = x, y = y, occupant = occupant };
|
||||
if (occupant != null) EnableDrag(cell, occupant, hold, -1);
|
||||
PositionInGrid(cell, x, y, 1, 1);
|
||||
}
|
||||
foreach (var item in items)
|
||||
{
|
||||
InventoryGrid.Size(item.itemId, item.rotated, out int w, out int h);
|
||||
string prefix = game.SelectedCargo.Contains(item.uid) ? "✓ " : "";
|
||||
var label = Text(grid, prefix + item.Name + (item.count > 1 ? " ×" + item.count : ""), "inventory-item");
|
||||
label.pickingMode = PickingMode.Ignore;
|
||||
if (item.uid == selectedUid) label.AddToClassList("selected");
|
||||
PositionInGrid(label, item.x, item.y, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void EnableDrag(Button button, CargoStack item, Hold hold, int slot)
|
||||
{
|
||||
// Captured pointer events can be dispatched directly to the pressed button.
|
||||
button.RegisterCallback<PointerMoveEvent>(DragMove, TrickleDown.TrickleDown);
|
||||
button.RegisterCallback<PointerUpEvent>(DragEnd, TrickleDown.TrickleDown);
|
||||
button.RegisterCallback<PointerDownEvent>(evt =>
|
||||
{
|
||||
if (evt.button != 0) return;
|
||||
dragItem = item;
|
||||
dragHold = hold;
|
||||
dragSlot = slot;
|
||||
dragStart = evt.position;
|
||||
dragPointer = evt.pointerId;
|
||||
}, TrickleDown.TrickleDown);
|
||||
}
|
||||
|
||||
void DragMove(PointerMoveEvent evt)
|
||||
{
|
||||
if (dragItem == null || evt.pointerId != dragPointer) return;
|
||||
Vector2 pos = evt.position;
|
||||
if (dragPreview == null)
|
||||
{
|
||||
if ((pos - dragStart).sqrMagnitude < 36) return;
|
||||
if (selectedUid != dragItem.uid)
|
||||
{
|
||||
quantity = dragItem.count;
|
||||
rotated = dragItem.rotated;
|
||||
}
|
||||
selectedUid = dragItem.uid;
|
||||
selectedSlot = dragSlot;
|
||||
selectedHold = dragHold;
|
||||
dragPreview = Text(root, dragItem.Name, "inventory-item selected");
|
||||
dragPreview.pickingMode = PickingMode.Ignore;
|
||||
dragPreview.style.position = UnityEngine.UIElements.Position.Absolute;
|
||||
SizePreview();
|
||||
}
|
||||
var target = root.panel.Pick(pos);
|
||||
Vector2 anchor = target?.userData is GridCell ? target.worldBound.min : pos;
|
||||
Vector2 local = root.WorldToLocal(anchor);
|
||||
dragPreview.style.left = local.x;
|
||||
dragPreview.style.top = local.y;
|
||||
}
|
||||
|
||||
void SizePreview()
|
||||
{
|
||||
InventoryGrid.Size(dragItem.itemId, rotated, out int w, out int h);
|
||||
dragPreview.style.width = CellWidth * w - 2;
|
||||
dragPreview.style.height = CellHeight * h - 2;
|
||||
}
|
||||
|
||||
void DragEnd(PointerUpEvent evt)
|
||||
{
|
||||
if (dragItem == null || evt.pointerId != dragPointer) return;
|
||||
bool dragged = dragPreview != null;
|
||||
ResetDrag();
|
||||
if (!dragged) return;
|
||||
evt.StopImmediatePropagation();
|
||||
var owner = root.panel.GetCapturingElement(evt.pointerId) as VisualElement;
|
||||
owner?.ReleasePointer(evt.pointerId);
|
||||
var target = root.panel.Pick(evt.position)?.userData as GridCell;
|
||||
if (target != null) InventoryClick(target.hold, target.x, target.y, target.occupant);
|
||||
else ShowCargo();
|
||||
}
|
||||
|
||||
void ResetDrag()
|
||||
{
|
||||
dragPreview?.RemoveFromHierarchy();
|
||||
dragPreview = null;
|
||||
dragItem = null;
|
||||
}
|
||||
|
||||
static void PositionInGrid(VisualElement element, int x, int y, int w, int h)
|
||||
{
|
||||
element.style.position = UnityEngine.UIElements.Position.Absolute;
|
||||
element.style.left = x * CellWidth;
|
||||
element.style.top = y * CellHeight;
|
||||
element.style.width = w * CellWidth - 2;
|
||||
element.style.height = h * CellHeight - 2;
|
||||
}
|
||||
|
||||
void InventoryClick(Hold hold, int x, int y, CargoStack occupant)
|
||||
{
|
||||
var item = Selection;
|
||||
if (item == null)
|
||||
{
|
||||
SelectInventoryItem(occupant, hold);
|
||||
return;
|
||||
}
|
||||
bool ok;
|
||||
if (selectedSlot >= 0)
|
||||
ok = game.Store.Uninstall(selectedSlot, hold, x, y, rotated);
|
||||
else if (occupant == null || item.uid == occupant.uid)
|
||||
ok = game.Store.MoveItem(item.uid, selectedHold, hold, x, y, rotated, quantity);
|
||||
else if (item.itemId == occupant.itemId && InventoryGrid.IsMaterial(item.itemId))
|
||||
ok = game.Store.MergeMaterial(item.uid, selectedHold, occupant.uid, hold, quantity);
|
||||
else
|
||||
ok = quantity == item.count && game.Store.SwapItems(item.uid, selectedHold, occupant.uid, hold, rotated);
|
||||
if (ok)
|
||||
{
|
||||
selectedUid = null;
|
||||
game.SelectedCargo.RemoveAll(uid => !game.Run.cargo.Exists(i => i.uid == uid));
|
||||
game.Pilot.ApplyLoadout();
|
||||
}
|
||||
else game.Notify(string.IsNullOrEmpty(game.Store.Error) ? "notice.placement" : "notice.saveFailed");
|
||||
ShowCargo();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6faff651d0fe95448b2711a92bce81d3
|
||||
Reference in New Issue
Block a user