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
+58 -98
View File
@@ -11,12 +11,14 @@ namespace Ironspore
public string uid = Guid.NewGuid().ToString("N");
public string itemId;
public int count;
public int x, y;
public bool rotated, borrowed;
public CargoStack(string id, int amount)
{
itemId = id;
count = amount;
}
public string Name => GameLocalization.ItemName(itemId);
public string Name => InventoryGrid.Slot(itemId) >= 0 ? GameLocalization.Text("equipment." + itemId + ".name") : GameLocalization.ItemName(itemId);
}
[Serializable]
@@ -39,13 +41,18 @@ namespace Ironspore
public float elapsed;
public float escortArmor = 250;
public List<CargoStack> cargo = new List<CargoStack>();
public List<CargoStack> safeCase = new List<CargoStack>();
public List<CargoStack> installed = new List<CargoStack>();
public bool destructionPending;
public List<string> searched = new List<string>();
}
[Serializable]
public sealed class GameSave
{
public int version = 1;
public const int CurrentVersion = 2;
public int version = CurrentVersion;
public List<CargoStack> warehouse = new List<CargoStack>();
public int alloy;
public int cells;
public int sorties;
@@ -57,7 +64,7 @@ namespace Ironspore
public RunRecord run = new RunRecord();
}
public sealed class ProfileStore
public sealed partial class ProfileStore
{
readonly string path;
public GameSave Data
@@ -69,10 +76,28 @@ namespace Ironspore
get; private set;
}
public static string SavePath(bool trial)
{
#if UNITY_EDITOR
string root = Path.Combine(Path.GetDirectoryName(Application.dataPath), "UserSettings");
#else
string root = Application.persistentDataPath;
#endif
return Path.Combine(root, "Ironspore", trial ? "range-v2.json" : "profile-v2.json");
}
public ProfileStore(string savePath)
{
path = savePath;
Data = Load(path) ?? Load(path + ".bak") ?? new GameSave();
Data = Load(path) ?? Load(path + ".bak");
if (Data == null)
{
if (File.Exists(path) || File.Exists(path + ".bak"))
throw new InvalidDataException("No valid equipment snapshot: " + path);
Data = new GameSave();
for (int slot = 0; slot < 4; slot++)
Data.warehouse.Add(new CargoStack(InventoryGrid.EquipmentId(slot, 0), 1));
}
}
static GameSave Load(string file)
@@ -82,19 +107,25 @@ namespace Ironspore
if (!File.Exists(file))
return null;
var save = JsonUtility.FromJson<GameSave>(File.ReadAllText(file));
if (save == null || save.version != 1 || save.run == null || save.receipts == null || save.blueprints == null || save.unlocked == null)
return null;
if (save.run.cargo == null || save.run.equipment == null || save.run.equipment.Length != 4)
return null;
if (save != null && save.version != GameSave.CurrentVersion)
throw new NotSupportedException("Unsupported equipment save version: " + save.version);
if (!Valid(save)) return null;
return save;
}
catch (Exception) { return null; }
catch (ArgumentException) { return null; }
}
public bool Commit(Func<GameSave, bool> change)
{
if (Data.run.destructionPending) return false;
return Write(change);
}
bool Write(Func<GameSave, bool> change)
{
Error = null;
var candidate = JsonUtility.FromJson<GameSave>(JsonUtility.ToJson(Data));
if (!change(candidate))
if (!change(candidate) || !Valid(candidate))
return false;
try
{
@@ -107,7 +138,7 @@ namespace Ironspore
stream.Flush(true);
}
if (File.Exists(path))
File.Replace(temp, path, path + ".bak");
File.Replace(temp, path, Load(path) == null ? null : path + ".bak");
else
File.Move(temp, path);
Data = candidate;
@@ -117,102 +148,31 @@ namespace Ironspore
catch (Exception ex) { Error = ex.Message; return false; }
}
public bool Begin(int chassis, int[] equipment, float armor, int seed)
public bool Begin(int chassis, int[] equipment, float armor, int seed, bool trial = false, string[] selected = null)
{
return Commit(s =>
{
if ((selected != null && selected.Length != 4) || s.run.active || equipment == null || equipment.Length != 4 || armor <= 0) return false;
s.run = new RunRecord { id = Guid.NewGuid().ToString("N"), active = true, seed = seed, chassis = chassis, equipment = (int[])equipment.Clone(), armor = armor };
for (int slot = 0; slot < 4; slot++)
{
string id = InventoryGrid.EquipmentId(slot, equipment[slot]);
if (id == null) return false;
var requested = selected?[slot];
var item = s.warehouse.Find(part => part.itemId == id && (requested == null || requested == part.uid));
if (item == null)
{
if (requested != null || (!trial && equipment[slot] != 0)) return false;
item = new CargoStack(id, 1) { borrowed = true };
}
else s.warehouse.Remove(item);
s.run.installed.Add(item);
}
s.sorties++;
return true;
});
}
public bool AddCargo(string id, int count, string sourceKey = "")
{
if (count <= 0)
return false;
return Commit(s =>
{
if (!s.run.active)
return false;
if (sourceKey.Length > 0 && s.run.searched.Contains(sourceKey))
return false;
foreach (var stack in s.run.cargo)
{
if (id.StartsWith("blueprint_") || stack.itemId != id || stack.count + count > 10)
continue;
stack.count += count;
if (sourceKey.Length > 0)
s.run.searched.Add(sourceKey);
return true;
}
if (s.run.cargo.Count >= 4 || (id.StartsWith("blueprint_") ? count != 1 : count > 10))
return false;
s.run.cargo.Add(new CargoStack(id, count));
if (sourceKey.Length > 0)
s.run.searched.Add(sourceKey);
return true;
});
}
static void Bank(GameSave s, CargoStack stack)
{
if (stack.itemId == "alloy")
s.alloy += stack.count;
else if (stack.itemId == "cells")
s.cells += stack.count;
else if (!s.blueprints.Contains(stack.itemId))
s.blueprints.Add(stack.itemId);
else
s.alloy += 2;
}
public bool SendCargo(IReadOnlyCollection<string> selection)
{
if (selection == null || selection.Count == 0 || selection.Count > 2)
return false;
var ids = new HashSet<string>(selection);
if (ids.Count != selection.Count)
return false;
return Commit(s =>
{
string receipt = s.run.id + ":cargo";
if (!s.run.active || s.run.armor <= 0 || s.run.podUsed || s.receipts.Contains(receipt))
return false;
var picked = s.run.cargo.FindAll(x => ids.Contains(x.uid));
if (picked.Count != ids.Count)
return false;
foreach (var stack in picked)
{
Bank(s, stack);
s.run.cargo.Remove(stack);
}
s.run.podUsed = true;
s.receipts.Add(receipt);
return true;
});
}
public bool Finish(bool destroyed)
{
return Commit(s =>
{
string receipt = s.run.id + ":return";
if (!s.run.active || s.receipts.Contains(receipt))
return false;
if (!destroyed)
{
foreach (var stack in s.run.cargo)
Bank(s, stack);
s.extractions++;
}
s.run.cargo.Clear();
s.run.active = false;
s.receipts.Add(receipt);
return true;
});
}
public bool CompleteRoute()
{
return Commit(s =>