using System; using System.Collections.Generic; namespace Ironspore { public sealed partial class ProfileStore { static bool Live(RunRecord run) => run.active && !run.destructionPending && run.armor > 0; static List Items(RunRecord r, Hold hold) => hold == Hold.Cargo ? r.cargo : r.safeCase; static int Capacity(Hold hold) => hold == Hold.Cargo ? InventoryGrid.CargoSize : InventoryGrid.CaseSize; static bool ValidHold(Hold hold) => hold == Hold.Cargo || hold == Hold.SafeCase; static bool Valid(GameSave s) { if (s?.run == null || s.receipts == null || s.blueprints == null || s.unlocked == null || s.warehouse == null || s.run.cargo == null || s.run.safeCase == null || s.run.installed == null || s.run.searched == null || s.run.equipment?.Length != 4 || s.version != GameSave.CurrentVersion) return false; var ids = new HashSet(); var slots = new HashSet(); bool Check(CargoStack i) => i != null && !string.IsNullOrEmpty(i.uid) && ids.Add(i.uid) && InventoryGrid.Known(i.itemId) && i.count > 0 && i.count <= (InventoryGrid.IsMaterial(i.itemId) ? 10 : 1); foreach (var i in s.warehouse) if (!Check(i) || i.borrowed || InventoryGrid.Slot(i.itemId) < 0) return false; foreach (var hold in new[] { Hold.Cargo, Hold.SafeCase }) { var list = Items(s.run, hold); foreach (var i in list) if (!Check(i) || !InventoryGrid.Fits(list, Capacity(hold), i, i.x, i.y, i.rotated)) return false; } foreach (var i in s.run.installed) { if (!Check(i)) return false; int slot = InventoryGrid.Slot(i.itemId); if (slot < 0 || !slots.Add(slot) || InventoryGrid.EquipmentId(slot, s.run.equipment[slot]) != i.itemId) return false; } if (s.run.active) { if (string.IsNullOrEmpty(s.run.id)) return false; for (int slot = 0; slot < 4; slot++) if (!slots.Contains(slot) && s.run.equipment[slot] != -1) return false; } else if (s.run.destructionPending || s.run.installed.Count + s.run.cargo.Count + s.run.safeCase.Count != 0) return false; return new HashSet(s.receipts).Count == s.receipts.Count; } public bool Manufacture(string blueprint) => Commit(s => { var recipe = InventoryGrid.Recipe(blueprint); if (s.run.active || recipe.item == null || !s.unlocked.Contains(blueprint) || s.alloy < recipe.alloy || s.cells < recipe.cells) return false; s.alloy -= recipe.alloy; s.cells -= recipe.cells; s.warehouse.Add(new CargoStack(recipe.item, 1)); return true; }); public bool AddCargo(string id, int count, string sourceKey = "", string offeredPart = null) { if (!InventoryGrid.Known(id) || count < 1 || count > (InventoryGrid.IsMaterial(id) ? 10 : 1)) return false; return Commit(s => { var r = s.run; if (!Live(r) || (!string.IsNullOrEmpty(sourceKey) && r.searched.Contains(sourceKey))) return false; int remaining = count; if (InventoryGrid.IsMaterial(id)) { foreach (var stack in r.cargo) { if (stack.itemId != id) continue; int added = Math.Min(10 - stack.count, remaining); stack.count += added; remaining -= added; if (remaining == 0) break; } } if (remaining > 0 && !InventoryGrid.AutoPlace(r.cargo, InventoryGrid.CargoSize, new CargoStack(id, remaining))) return false; if (offeredPart != null && (InventoryGrid.Slot(offeredPart) < 0 || !InventoryGrid.AutoPlace(r.cargo, InventoryGrid.CargoSize, new CargoStack(offeredPart, 1)))) return false; if (!string.IsNullOrEmpty(sourceKey)) r.searched.Add(sourceKey); return true; }); } // Omitting coordinates requests first-fit placement. Explicit placement also rotates in place. public bool MoveItem(string uid, Hold from, Hold to, int x = -1, int y = -1, bool rotated = false, int quantity = 0) { if (!ValidHold(from) || !ValidHold(to)) return false; return Commit(s => { if (!Live(s.run)) return false; var source = Items(s.run, from); var item = source.Find(i => i.uid == uid); if (item == null || quantity < 0 || quantity > item.count) return false; if (quantity == 0 || quantity == item.count) source.Remove(item); else { item.count -= quantity; item = new CargoStack(item.itemId, quantity); } var target = Items(s.run, to); return x == -1 && y == -1 ? InventoryGrid.AutoPlace(target, Capacity(to), item) : InventoryGrid.Place(target, Capacity(to), item, x, y, rotated); }); } public bool SwapItems(string uid, Hold from, string otherUid, Hold to, bool rotated) { if (!ValidHold(from) || !ValidHold(to) || uid == otherUid) return false; return Commit(s => { if (!Live(s.run)) return false; var source = Items(s.run, from); var target = Items(s.run, to); var first = source.Find(i => i.uid == uid); var second = target.Find(i => i.uid == otherUid); if (first == null || second == null) return false; int x = first.x, y = first.y; source.Remove(first); target.Remove(second); return InventoryGrid.Place(target, Capacity(to), first, second.x, second.y, rotated) && InventoryGrid.Place(source, Capacity(from), second, x, y, second.rotated); }); } public bool MergeMaterial(string sourceUid, Hold from, string targetUid, Hold to, int quantity) { if (!ValidHold(from) || !ValidHold(to) || sourceUid == targetUid || quantity < 1) return false; return Commit(s => { if (!Live(s.run)) return false; var source = Items(s.run, from).Find(i => i.uid == sourceUid); var target = Items(s.run, to).Find(i => i.uid == targetUid); if (source == null || target == null || source.itemId != target.itemId || !InventoryGrid.IsMaterial(source.itemId) || quantity > source.count || quantity > 10 - target.count) return false; source.count -= quantity; target.count += quantity; if (source.count == 0) Items(s.run, from).Remove(source); return true; }); } public bool Uninstall(int slot, Hold to, int x, int y, bool rotated) { if (!ValidHold(to)) return false; return Commit(s => { var r = s.run; if (!Live(r) || slot < 0 || slot > 3) return false; var item = r.installed.Find(i => InventoryGrid.Slot(i.itemId) == slot); if (item == null || !InventoryGrid.Place(Items(r, to), Capacity(to), item, x, y, rotated)) return false; r.installed.Remove(item); r.equipment[slot] = -1; return true; }); } public bool InstallItem(string uid, Hold from, Func fitsBudget) { if (!ValidHold(from) || fitsBudget == null) return false; return Commit(s => { var r = s.run; if (!Live(r)) return false; var source = Items(r, from); var item = source.Find(i => i.uid == uid); if (item == null) return false; int slot = InventoryGrid.Slot(item.itemId); if (slot < 0) return false; r.equipment[slot] = InventoryGrid.Index(slot, item.itemId); if (!fitsBudget(r.equipment)) return false; source.Remove(item); var old = r.installed.Find(i => InventoryGrid.Slot(i.itemId) == slot); if (old != null) { if (!InventoryGrid.AutoPlace(source, Capacity(from), old)) return false; r.installed.Remove(old); } r.installed.Add(item); return true; }); } static void Bank(GameSave s, CargoStack item) { if (item.borrowed) return; if (InventoryGrid.Slot(item.itemId) >= 0) s.warehouse.Add(item); else if (item.itemId == "alloy") s.alloy += item.count; else if (item.itemId == "cells") s.cells += item.count; else if (!s.blueprints.Contains(item.itemId)) s.blueprints.Add(item.itemId); else s.alloy += 2; } static List Manifest(RunRecord r, IReadOnlyCollection selection) { if (!Live(r) || r.podUsed || selection == null || selection.Count == 0 || selection.Count > 4) return null; var ids = new HashSet(selection); var picked = r.cargo.FindAll(i => ids.Contains(i.uid)); return picked.Count == selection.Count && InventoryGrid.PodFits(picked) ? picked : null; } public bool CanSendCargo(IReadOnlyCollection selection) => Manifest(Data.run, selection) != null; public bool SendCargo(IReadOnlyCollection selection) => Commit(s => { string receipt = s.run.id + ":cargo"; var picked = Manifest(s.run, selection); if (picked == null || s.receipts.Contains(receipt)) return false; foreach (var item in picked) { Bank(s, item); s.run.cargo.Remove(item); } s.run.podUsed = true; s.receipts.Add(receipt); return true; }); public bool MarkDestroyed() => Commit(s => { if (!s.run.active) return false; s.run.armor = 0; s.run.destructionPending = true; return true; }); public bool Finish(bool destroyed) { if (destroyed && !Data.run.destructionPending && !MarkDestroyed()) return false; return Write(s => { var r = s.run; string receipt = r.id + ":return"; if (!r.active || r.destructionPending != destroyed || s.receipts.Contains(receipt)) return false; if (destroyed) s.receipts.Add(r.id + ":secure_eject"); else { if (r.armor <= 0) return false; foreach (var item in r.cargo) Bank(s, item); foreach (var item in r.installed) Bank(s, item); s.extractions++; } foreach (var item in r.safeCase) Bank(s, item); r.safeCase.Clear(); r.cargo.Clear(); r.installed.Clear(); r.active = false; r.destructionPending = false; s.receipts.Add(receipt); return true; }); } } }