Route trade purchases through checkpoints
This commit is contained in:
parent
71854495d2
commit
f58e738cf8
5 changed files with 166 additions and 61 deletions
Binary file not shown.
|
|
@ -19,6 +19,7 @@ RequireHullConductivityCommitAuthority(root);
|
|||
RequireInventoryEntityRenameCommitAuthority(root);
|
||||
RequireWeaponGroupCommitAuthority(root);
|
||||
RequireInventoryDoubleClickTransferCommitAuthority(root);
|
||||
RequireTradePurchaseCommitAuthority(root);
|
||||
|
||||
await using var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-state-verify");
|
||||
|
||||
|
|
@ -467,6 +468,7 @@ Console.WriteLine("Hull conductivity authority: inventory UI toggles flow throug
|
|||
Console.WriteLine("Inventory entity rename authority: UI rename flows through gameplay checkpoint commits");
|
||||
Console.WriteLine("Weapon group authority: UI assignment flows through gameplay checkpoint commits");
|
||||
Console.WriteLine("Inventory transfer authority: UI transfer and drag/drop requests flow through gameplay checkpoint commits");
|
||||
Console.WriteLine("Trade purchase authority: UI buy requests flow through gameplay checkpoint commits");
|
||||
|
||||
static void RequireCount(AetheriaMigrationLedger ledger, string documentType, int actual)
|
||||
{
|
||||
|
|
@ -1005,6 +1007,64 @@ static void RequireInventoryDoubleClickTransferCommitAuthority(string root)
|
|||
}
|
||||
}
|
||||
|
||||
static void RequireTradePurchaseCommitAuthority(string root)
|
||||
{
|
||||
var actionGameManagerPath = Path.Combine(root, "Assets", "Scripts", "Gameplay", "ActionGameManager.cs");
|
||||
var actionGameManager = File.Exists(actionGameManagerPath)
|
||||
? File.ReadAllText(actionGameManagerPath)
|
||||
: throw new InvalidOperationException("Cannot verify trade purchase authority; ActionGameManager.cs is missing.");
|
||||
|
||||
var requiredCommits = new[]
|
||||
{
|
||||
"CommitTradePurchase",
|
||||
"QueueRunCheckpoint(\"trade-purchase\")",
|
||||
"Credits -=",
|
||||
"new Ship("
|
||||
};
|
||||
|
||||
var missingCommits = requiredCommits
|
||||
.Where(symbol => !actionGameManager.Contains(symbol, StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
|
||||
if (missingCommits.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Trade purchase no longer has a gameplay-owned checkpoint commit primitive: " +
|
||||
string.Join(", ", missingCommits));
|
||||
}
|
||||
|
||||
var tradeMenuPath = Path.Combine(root, "Assets", "Scripts", "UI", "Menu", "TradeMenu.cs");
|
||||
var tradeMenu = File.Exists(tradeMenuPath)
|
||||
? File.ReadAllText(tradeMenuPath)
|
||||
: throw new InvalidOperationException("Cannot verify trade purchase authority; TradeMenu.cs is missing.");
|
||||
|
||||
var forbiddenSymbols = new[]
|
||||
{
|
||||
"GameManager.Credits -=",
|
||||
"Inventory.TryTransferItem(",
|
||||
"new Ship(",
|
||||
".SetParent(GameManager.DockedEntity)"
|
||||
};
|
||||
|
||||
var hits = File.ReadLines(tradeMenuPath)
|
||||
.Select((line, index) => new { LineNumber = index + 1, Line = line })
|
||||
.Where(line => forbiddenSymbols.Any(symbol => line.Line.Contains(symbol, StringComparison.Ordinal)))
|
||||
.Select(line => $"{Path.GetRelativePath(root, tradeMenuPath)}:{line.LineNumber}: {line.Line.Trim()}")
|
||||
.ToArray();
|
||||
|
||||
if (hits.Length > 0)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"TradeMenu still owns direct purchase mutation: " +
|
||||
string.Join("; ", hits));
|
||||
}
|
||||
|
||||
if (!tradeMenu.Contains("GameManager.CommitTradePurchase", StringComparison.Ordinal))
|
||||
{
|
||||
throw new InvalidOperationException("TradeMenu no longer routes purchases through ActionGameManager.");
|
||||
}
|
||||
}
|
||||
|
||||
static string ExtractMethodBody(string source, string methodName)
|
||||
{
|
||||
var signature = "private void " + methodName + "()";
|
||||
|
|
|
|||
|
|
@ -1675,6 +1675,76 @@ public class ActionGameManager : MonoBehaviour
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool CommitTradePurchase(
|
||||
EquippedCargoBay stationInventory,
|
||||
EquippedCargoBay targetCargo,
|
||||
CraftedItemInstance item,
|
||||
int price,
|
||||
bool createsDockedShip)
|
||||
{
|
||||
if (item == null ||
|
||||
price < 0 ||
|
||||
price > Credits)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (createsDockedShip)
|
||||
{
|
||||
if (!(item is EquippableItem hull) ||
|
||||
Zone == null ||
|
||||
DockedEntity == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var ship = new Ship(ItemManager, Zone, hull, NewEntitySettings) { IsPlayerShip = true };
|
||||
ship.SetParent(DockedEntity);
|
||||
Credits -= price;
|
||||
QueueRunCheckpoint("trade-purchase");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (stationInventory == null ||
|
||||
targetCargo == null ||
|
||||
!stationInventory.TryTransferItem(targetCargo, item))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Credits -= price;
|
||||
QueueRunCheckpoint("trade-purchase");
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool CommitTradePurchase(
|
||||
EquippedCargoBay stationInventory,
|
||||
EquippedCargoBay targetCargo,
|
||||
SimpleCommodity item,
|
||||
int quantity,
|
||||
int unitPrice)
|
||||
{
|
||||
if (stationInventory == null ||
|
||||
targetCargo == null ||
|
||||
item == null ||
|
||||
quantity <= 0 ||
|
||||
unitPrice < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var totalPrice = (long)quantity * unitPrice;
|
||||
if (totalPrice > Credits || totalPrice > int.MaxValue)
|
||||
return false;
|
||||
|
||||
if (!stationInventory.TryTransferItem(targetCargo, item, quantity))
|
||||
return false;
|
||||
|
||||
Credits -= (int)totalPrice;
|
||||
QueueRunCheckpoint("trade-purchase");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CommitEquippedItemOverrideShutdown(EquippedItem item, bool enabled)
|
||||
{
|
||||
if (item?.EquippableItem == null)
|
||||
|
|
|
|||
|
|
@ -528,41 +528,27 @@ public class TradeMenu : MonoBehaviour
|
|||
}
|
||||
|
||||
var price = GetTypedTradePrice(item, typedItem);
|
||||
if (price < GameManager.Credits)
|
||||
var isShipHull = !string.IsNullOrWhiteSpace(typedItem?.HullType);
|
||||
if (isShipHull &&
|
||||
!string.Equals(typedItem.HullType, nameof(HullType.Ship), StringComparison.Ordinal))
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(typedItem?.HullType))
|
||||
{
|
||||
if (!string.Equals(typedItem.HullType, nameof(HullType.Ship), StringComparison.Ordinal))
|
||||
throw new ArgumentException("Attempted to buy non-ship hull from station, WTF are you doing?!");
|
||||
|
||||
var ship = new Ship(GameManager.ItemManager, GameManager.Zone, item as EquippableItem, GameManager.NewEntitySettings) { IsPlayerShip = true };
|
||||
ship.SetParent(GameManager.DockedEntity);
|
||||
|
||||
GameManager.Credits -= price;
|
||||
UpdateCreditsLabel();
|
||||
}
|
||||
else if (Inventory.TryTransferItem(_targetCargo, item))
|
||||
{
|
||||
GameManager.Credits -= price;
|
||||
UpdateCreditsLabel();
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialog.Clear();
|
||||
Dialog.Title.text = "Unable to buy: Insufficient Cargo Space!";
|
||||
Dialog.Show();
|
||||
Dialog.MoveToCursor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialog.Clear();
|
||||
Dialog.Title.text = "Unable to buy: Insufficient Credits!";
|
||||
Dialog.Show();
|
||||
Dialog.MoveToCursor();
|
||||
ShowUnableToBuy("Unsupported hull purchase!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (price > GameManager.Credits)
|
||||
{
|
||||
ShowUnableToBuy("Insufficient Credits!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GameManager.CommitTradePurchase(Inventory, _targetCargo, item, price, isShipHull))
|
||||
{
|
||||
ShowUnableToBuy(isShipHull ? "Unable to create ship!" : "Insufficient Cargo Space!");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateCreditsLabel();
|
||||
}
|
||||
|
||||
private void Buy(SimpleCommodity simpleCommodity, int quantity)
|
||||
|
|
@ -574,40 +560,22 @@ public class TradeMenu : MonoBehaviour
|
|||
return;
|
||||
}
|
||||
|
||||
var maxStack = typedItem.MaxStack > 0 ? typedItem.MaxStack : 1;
|
||||
var price = typedItem.Price;
|
||||
// Up-rounded integer division from https://stackoverflow.com/a/503201
|
||||
int lots = (quantity - 1) / maxStack + 1;
|
||||
int remaining = quantity;
|
||||
for (int i = 0; i < lots; i++)
|
||||
var clampedQuantity = min(quantity, simpleCommodity.Quantity);
|
||||
var totalPrice = (long)clampedQuantity * price;
|
||||
if (totalPrice > GameManager.Credits)
|
||||
{
|
||||
int q = min(remaining, maxStack);
|
||||
if (q * price < GameManager.Credits)
|
||||
{
|
||||
if (Inventory.TryTransferItem(_targetCargo, simpleCommodity, quantity))
|
||||
{
|
||||
GameManager.Credits -= q * price;
|
||||
UpdateCreditsLabel();
|
||||
remaining -= q;
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialog.Clear();
|
||||
Dialog.Title.text = "Unable to buy: Insufficient Cargo Space!";
|
||||
Dialog.Show();
|
||||
Dialog.MoveToCursor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Dialog.Clear();
|
||||
Dialog.Title.text = "Unable to buy: Insufficient Credits!";
|
||||
Dialog.Show();
|
||||
Dialog.MoveToCursor();
|
||||
return;
|
||||
}
|
||||
ShowUnableToBuy("Insufficient Credits!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!GameManager.CommitTradePurchase(Inventory, _targetCargo, simpleCommodity, clampedQuantity, price))
|
||||
{
|
||||
ShowUnableToBuy("Insufficient Cargo Space!");
|
||||
return;
|
||||
}
|
||||
|
||||
UpdateCreditsLabel();
|
||||
}
|
||||
|
||||
private int GetTypedTradePrice(CraftedItemInstance item, AetheriaRuntimeCatalogItem typedItem)
|
||||
|
|
|
|||
|
|
@ -1004,6 +1004,11 @@ First Aetheria surfaces to publish:
|
|||
- Done: move trade menu buy price, ship-hull classification, and simple
|
||||
commodity stack-size decisions onto typed catalog rows; inventory transfer
|
||||
and ship construction remain the runtime mutation owners.
|
||||
- Done: route trade purchase mutation through gameplay-owned checkpoint
|
||||
commits. `TradeMenu` still presents typed trade rows and calculates display
|
||||
prices, but no longer subtracts credits, transfers purchased cargo, or
|
||||
constructs purchased ships directly. `ActionGameManager.CommitTradePurchase`
|
||||
owns the purchase mutation and queues a typed run checkpoint.
|
||||
- Done: move TradeMenu dynamic behavior columns onto typed behavior payload
|
||||
fields; the menu no longer hydrates `ItemData` or `BehaviorData` for row
|
||||
display, filtering, sorting, or buy decisions.
|
||||
|
|
@ -1719,7 +1724,9 @@ collects text, gameplay owns the name mutation and checkpoint. Weapon group
|
|||
assignment follows the same rule: UI requests membership, gameplay owns the
|
||||
group mutation and checkpoint. Inventory double-click transfer follows the same
|
||||
rule, and drag/drop placement now shares that commit family: UI requests
|
||||
cargo/equipment movement, gameplay owns the move and queues the checkpoint. Any
|
||||
cargo/equipment movement, gameplay owns the move and queues the checkpoint.
|
||||
Trade purchases follow the same rule: UI requests the purchase, gameplay owns
|
||||
credit changes, cargo transfer, ship creation, and checkpoint. Any
|
||||
predicate that still needs legacy DTO objects must earn that dependency by
|
||||
using behavior objects or simulation-only methods that typed facets do not yet
|
||||
expose.
|
||||
|
|
|
|||
Loading…
Reference in a new issue