using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Execution;
/// <summary>Explicit restrictions for real trade acceptance; ordinary user routes keep their own capacity.</summary>
public sealed class TradeAcceptancePolicy(IReadOnlyList<InventoryItem>? reservedCargo=null)
{
private readonly InventoryItem[] reserved=[..reservedCargo??[]];
public const long MaximumUnitPrice=500;
private static readonly HashSet<string> CheapGoods=new(StringComparer.OrdinalIgnoreCase)
{"algae","biowaste","foodcartridges","grain","hydrogenfuel","scrap","water"};
public static bool Allows(string commodity,long unitPrice)=>CheapGoods.Contains(commodity)&&unitPrice is >0 and <=MaximumUnitPrice;
public void ValidatePlan(RoutePlan plan,PlayerState player)
{
if(plan.Request.CargoCapacity!=1||plan.Legs.Count==0)throw new InvalidOperationException("实机跑商测试每次只能运输 1t。");
if(!CargoMatches(player,null))
throw new InvalidOperationException("测试需要空货舱;不会出售、丢弃或覆盖原有货物。");
foreach(var leg in plan.Legs)
{
if(leg.Orders.Count!=1||leg.Quantity!=1||leg.Orders[0].Quantity!=1||!Allows(leg.Orders[0].Commodity,leg.Orders[0].BuyPrice))
throw new InvalidOperationException("测试只允许 1t 常见低价货物,单价不得超过 500 Cr。");
}
}
public void ValidateAction(TradeAction action,PlayerState player)
{
if(!player.CargoReliable)throw new InvalidOperationException("货舱状态无法核实,已停止测试。");
var order=action.Order;
if(action.Leg.Orders.Count!=1||action.Leg.Quantity!=1||!Allows(action.Leg.Orders[0].Commodity,action.Leg.Orders[0].BuyPrice))
throw new InvalidOperationException("测试航段超出 1t 低价货物限制。");
if(action.Kind==TradeActionKind.Travel)
{
RequireCargo(player,action.Leg.Orders[0].Commodity);
return;
}
if(order is null||order!=action.Leg.Orders[0]||order.Quantity!=1||!Allows(order.Commodity,order.BuyPrice))
throw new InvalidOperationException("测试交易指令与获准的 1t 低价货物不一致。");
if(action.Kind==TradeActionKind.Buy)
{
if(!player.Docked||player.MarketId!=action.Leg.From.MarketId||!CargoMatches(player,null))
throw new InvalidOperationException("买入前站点或空货舱状态改变,已停止测试。");
}
else
{
if(!player.Docked||player.MarketId!=action.Leg.To.MarketId)throw new InvalidOperationException("当前不是测试卖出站点。");
RequireCargo(player,order.Commodity);
}
}
private void RequireCargo(PlayerState player,string commodity)
{
if(!CargoMatches(player,commodity))
throw new InvalidOperationException("测试货舱必须恰好为指定的 1t 合法货物。");
}
private bool CargoMatches(PlayerState player,string? testCommodity)
{
// Existing limpets are equipment consumables. Preserve their exact count;
// no existing commodity, mission cargo or stolen item can become a test sale.
if(reserved.Any(x=>!x.Key.Equals("drones",StringComparison.OrdinalIgnoreCase)||x.Stolen!=0||x.MissionId is not null||x.Count<0))return false;
var expected=reserved.Where(x=>x.Count>0).ToList();
if(testCommodity is not null)expected.Add(new(testCommodity,testCommodity,1));
if(!player.CargoReliable||player.CargoCount!=expected.Sum(x=>x.Count)||player.Inventory.Any(x=>x.Count>0&&(x.Stolen!=0||x.MissionId is not null)))return false;
var actual=player.Inventory.Where(x=>x.Count>0).GroupBy(x=>x.Key,StringComparer.OrdinalIgnoreCase).ToDictionary(x=>x.Key,x=>x.Sum(i=>i.Count),StringComparer.OrdinalIgnoreCase);
var totals=expected.GroupBy(x=>x.Key,StringComparer.OrdinalIgnoreCase).ToDictionary(x=>x.Key,x=>x.Sum(i=>i.Count),StringComparer.OrdinalIgnoreCase);
return actual.Count==totals.Count&&totals.All(x=>actual.GetValueOrDefault(x.Key)==x.Value);
}
}
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Core.Execution;
/// <summary>Explicit restrictions for real trade acceptance; ordinary user routes keep their own capacity.</summary>
public sealed class TradeAcceptancePolicy(IReadOnlyList<InventoryItem>? reservedCargo=null)
{
private readonly InventoryItem[] reserved=[..reservedCargo??[]];
public const long MaximumUnitPrice=500;
private static readonly HashSet<string> CheapGoods=new(StringComparer.OrdinalIgnoreCase)
{"algae","biowaste","foodcartridges","grain","hydrogenfuel","scrap","water"};
public static bool Allows(string commodity,long unitPrice)=>CheapGoods.Contains(commodity)&&unitPrice is >0 and <=MaximumUnitPrice;
public void ValidatePlan(RoutePlan plan,PlayerState player)
{
if(plan.Request.CargoCapacity!=1||plan.Legs.Count==0)throw new InvalidOperationException("实机跑商测试每次只能运输 1t。");
if(!CargoMatches(player,null))
throw new InvalidOperationException("测试需要空货舱;不会出售、丢弃或覆盖原有货物。");
foreach(var leg in plan.Legs)
{
if(leg.Orders.Count!=1||leg.Quantity!=1||leg.Orders[0].Quantity!=1||!Allows(leg.Orders[0].Commodity,leg.Orders[0].BuyPrice))
throw new InvalidOperationException("测试只允许 1t 常见低价货物,单价不得超过 500 Cr。");
}
}
public void ValidateAction(TradeAction action,PlayerState player)
{
if(!player.CargoReliable)throw new InvalidOperationException("货舱状态无法核实,已停止测试。");
var order=action.Order;
if(action.Leg.Orders.Count!=1||action.Leg.Quantity!=1||!Allows(action.Leg.Orders[0].Commodity,action.Leg.Orders[0].BuyPrice))
throw new InvalidOperationException("测试航段超出 1t 低价货物限制。");
if(action.Kind==TradeActionKind.Travel)
{
RequireCargo(player,action.Leg.Orders[0].Commodity);
return;
}
if(order is null||order!=action.Leg.Orders[0]||order.Quantity!=1||!Allows(order.Commodity,order.BuyPrice))
throw new InvalidOperationException("测试交易指令与获准的 1t 低价货物不一致。");
if(action.Kind==TradeActionKind.Buy)
{
if(!player.Docked||player.MarketId!=action.Leg.From.MarketId||!CargoMatches(player,null))
throw new InvalidOperationException("买入前站点或空货舱状态改变,已停止测试。");
}
else
{
if(!player.Docked||player.MarketId!=action.Leg.To.MarketId)throw new InvalidOperationException("当前不是测试卖出站点。");
RequireCargo(player,order.Commodity);
}
}
private void RequireCargo(PlayerState player,string commodity)
{
if(!CargoMatches(player,commodity))
throw new InvalidOperationException("测试货舱必须恰好为指定的 1t 合法货物。");
}
private bool CargoMatches(PlayerState player,string? testCommodity)
{
// Existing limpets are equipment consumables. Preserve their exact count;
// no existing commodity, mission cargo or stolen item can become a test sale.
if(reserved.Any(x=>!x.Key.Equals("drones",StringComparison.OrdinalIgnoreCase)||x.Stolen!=0||x.MissionId is not null||x.Count<0))return false;
var expected=reserved.Where(x=>x.Count>0).ToList();
if(testCommodity is not null)expected.Add(new(testCommodity,testCommodity,1));
if(!player.CargoReliable||player.CargoCount!=expected.Sum(x=>x.Count)||player.Inventory.Any(x=>x.Count>0&&(x.Stolen!=0||x.MissionId is not null)))return false;
var actual=player.Inventory.Where(x=>x.Count>0).GroupBy(x=>x.Key,StringComparer.OrdinalIgnoreCase).ToDictionary(x=>x.Key,x=>x.Sum(i=>i.Count),StringComparer.OrdinalIgnoreCase);
var totals=expected.GroupBy(x=>x.Key,StringComparer.OrdinalIgnoreCase).ToDictionary(x=>x.Key,x=>x.Sum(i=>i.Count),StringComparer.OrdinalIgnoreCase);
return actual.Count==totals.Count&&totals.All(x=>actual.GetValueOrDefault(x.Key)==x.Value);
}
}