Fix faction ammunition resupply and settings recovery
Wait for both convoy cargo phases and use actual magazine stock. Resolve empty primary weapon ammunition from muzzle metadata and recover cleanly from failed initial settings saves. Update native fixtures, scoped cleanup guards, packaging evidence and validation documentation. Make reviewed fixture tests checkout-relative. All repository checks pass against the isolated staged tree; native resupply completion and multiplayer qualification remain outstanding.
This commit is contained in:
@@ -10,16 +10,18 @@ def cpp(s):
|
||||
s=s.replace('array<IEntity> items = {};','Array<IEntity> items;').replace('array<IEntity> raw = {};','Array<IEntity> raw;').replace('array<AIAgent> agents = {};','Array<AIAgent> agents;')
|
||||
s=s.replace('out array<IEntity> items','Array<IEntity>& items').replace('array<IEntity> items','Array<IEntity>& items')
|
||||
s=re.sub(r'foreach \((\w+) (\w+) : ([^)]+)\)',r'for (\1 \2 : \3)',s)
|
||||
for typ in ('InventoryStorageManagerComponent','ERR_ForceRecord'):
|
||||
for typ in ('InventoryStorageManagerComponent','ERR_ForceRecord','ERR_SupplyConvoyTask'):
|
||||
s=re.sub(r'\b'+typ+r' (?=\w)',typ+'* ',s)
|
||||
for name in ('ERR_ForceRules','ERR_InfantryResupplyRules','ERR_ConvoyRules','ERR_ForceKind','ERR_ConvoyResources','ERR_Inventory','ERR_Actors','SCR_FactionManager','CompartmentAccessComponent','Replication','Math'):
|
||||
s=s.replace(name+'.',name+'::')
|
||||
s=s.replace('ERR_SupplyConvoyTask.Cast','ERR_SupplyConvoyTask::Cast').replace('FindComponent(BaseMagazineComponent)','HasMagazine()')
|
||||
s=s.replace('vector.DistanceXZ','Vec::DistanceXZ').replace('InventoryStorageManagerComponent.Cast','InventoryStorageManagerComponent::Cast').replace('FindComponent(InventoryStorageManagerComponent)','FindComponent()')
|
||||
for name in ('m_Mode','m_HQ','m_Convoy','m_Truck','m_Recipient','m_Donor','m_Move','recipient','donor','demand','donorStatus','entry','operation','inventory','item','owner','record','actor','agent','unit','m_MoveWaypoint','m_HoldWaypoint','magazine'):
|
||||
for name in ('m_Mode','m_HQ','m_Convoy','m_Truck','m_Recipient','m_Donor','m_Move','recipient','donor','demand','donorStatus','entry','operation','inventory','item','owner','record','actor','agent','unit','m_MoveWaypoint','m_HoldWaypoint','magazine','supply'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
for name in ('Receipt','Entity','Group','Utility','StatusSampler','AmmoState','Combat','m_Perception'):
|
||||
s=s.replace(name+'.',name+'->')
|
||||
for name in ('Forces()','GetPrefabData()'):s=s.replace(name+'.',name+'->')
|
||||
s=s.replace('ERR_SupplyConvoyTask* supply', 'Convoy* supply')
|
||||
s=s.replace('bool available;','bool available=false;').replace('bool living;','bool living=false;')
|
||||
return s
|
||||
rules='\n'.join(cpp(body(P,x)) for x in ('static bool Fresh(', 'static bool Need(', 'static bool Pair('))
|
||||
@@ -45,7 +47,7 @@ struct Entity;using IEntity=Entity*;
|
||||
struct InventoryStorageManagerComponent{Array<IEntity>items;void GetItems(Array<IEntity>& out){out=items;}static auto Cast(InventoryStorageManagerComponent* p){return p;}};
|
||||
struct Prefab{string name="magazine";string GetPrefabName(){return name;}};
|
||||
struct Group;using SCR_AIGroup=Group*;
|
||||
struct Entity{Prefab prefab;bool hasPrefab=true,alive=true,player=false,fight=true,aboard=false,captured=false,released=false;int faction=1;Group* group=nullptr;InventoryStorageManagerComponent* inventory=nullptr;Prefab* GetPrefabData(){return hasPrefab?&prefab:nullptr;}InventoryStorageManagerComponent* FindComponent(){return inventory;}};
|
||||
struct Entity{Prefab prefab;bool magazine=true,hasPrefab=true,alive=true,player=false,fight=true,aboard=false,captured=false,released=false;int faction=1;Group* group=nullptr;InventoryStorageManagerComponent* inventory=nullptr;bool HasMagazine(){return magazine;}Prefab* GetPrefabData(){return hasPrefab?&prefab:nullptr;}InventoryStorageManagerComponent* FindComponent(){return inventory;}};
|
||||
struct Agent{IEntity actor;IEntity GetControlledEntity(){return actor;}};using AIAgent=Agent*;
|
||||
struct Waypoint{};
|
||||
struct Group{Array<AIAgent>agents;bool slave=false;int players=0,faction=1;bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}int GetFaction(){return faction;}void GetAgents(Array<AIAgent>& out){out=agents;}void RemoveWaypoint(Waypoint*){}};
|
||||
@@ -72,7 +74,8 @@ struct Combat{bool Held=false;};
|
||||
struct Truck{int Index=1,SupplyToken=0;bool Registered=true,Released=false,Arrived=false,SupplyHeld=false,crew=true;struct Entity* Entity=nullptr;struct Group* Group=nullptr;struct Combat* Combat=nullptr;bool CrewValid(Mode*){return crew;}};
|
||||
struct Receipt{bool Consumed=true;Array<int>Records={1,2,3,4,5,6};};
|
||||
struct Cargo{bool Finished=true;};using ERR_ConvoyCargoCallback=Cargo*;
|
||||
struct Convoy{bool Stopped=false,Ready=true;struct Receipt* Receipt=nullptr;Array<Truck*>Vehicles;Array<struct Cargo*>Cargo;float Deadline=30000;};
|
||||
struct Convoy{bool supply=false,MagazinesIssued=true,CargoIssued=true,Stopped=false,Ready=true;struct Receipt* Receipt=nullptr;Array<Truck*>Vehicles;Array<struct Cargo*>Cargo;float Deadline=30000;};
|
||||
struct ERR_SupplyConvoyTask:Convoy{static Convoy* Cast(Convoy* task){return task&&task->supply?task:nullptr;}};
|
||||
struct ERR_ConvoyRules{static bool Captured(Entity* e,Group*){return e->captured;}};
|
||||
struct ERR_ConvoyResources{static string Magazine(){return "magazine";}};
|
||||
struct Route{};struct Leaf{void Stop(){}};
|
||||
@@ -81,7 +84,7 @@ struct HQ{Mode* m_Mode;Array<ERR_HQUnit>m_Units;Convoy* convoy=nullptr;Route rou
|
||||
'''+reserve+r'''
|
||||
};
|
||||
struct Service{HQ* m_HQ;Mode* m_Mode;Convoy* m_Convoy=nullptr;Truck* m_Truck=nullptr;Unit* m_Recipient=nullptr;Unit* m_Donor=nullptr;Leaf* m_Move=nullptr;Route* m_Order=nullptr;IEntity m_Receiving=nullptr;
|
||||
Array<IEntity>m_Stock,m_Spent,m_Members,m_Served;Map<int>m_Consumed;Map<float>m_NextAttempt;int m_State=0,m_Trip=0,m_Token=0,m_ReportId=0,m_TripDelivered=0;float m_Deadline=0;bool m_Stopped=false;
|
||||
Array<IEntity>m_Stock,m_Spent,m_Members,m_Served;Map<int>m_Consumed;Map<float>m_NextAttempt;int m_State=0,m_Trip=0,m_Token=0,m_ReportId=0,m_TripDelivered=0;float m_Deadline=0,m_StockDeadline=-1;bool m_Stopped=false;
|
||||
Waypoint* m_MoveWaypoint=nullptr;Waypoint* m_HoldWaypoint=nullptr;string reason;bool Current(){return !m_Stopped&&Replication::IsServer()&&m_HQ->active;}void Audit(string why){reason=why;}
|
||||
'''+methods+r'''
|
||||
};
|
||||
@@ -103,14 +106,33 @@ int main(){
|
||||
// Actual inventory aggregation collapses repeated native storage references, not distinct identical-prefab magazines.
|
||||
{Fixture f;f.inventory.items.Insert(&f.items[0]);f.inventory.items.Insert(nullptr);f.s.Stock(0);assert(f.s.m_State==1&&!f.s.m_Stopped&&f.s.m_Stock.Count()==48);assert(f.s.m_Stock[0]!=f.s.m_Stock[1]);}
|
||||
// Malformed/non-magazine items never become spendable stock; native startup partial cargo stays partial.
|
||||
{Fixture f;f.inventory.items={&f.items[0],&f.items[1],&f.items[2],nullptr};f.items[1].hasPrefab=false;f.items[2].prefab.name="rifle";f.s.Stock(0);assert(f.s.m_Stock.Count()==1&&f.s.m_State==1);}
|
||||
{Fixture f;f.cargo.Finished=false;f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());f.s.Stock(30000);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
|
||||
{Fixture f;f.inventory.items={&f.items[0],&f.items[1],&f.items[2],nullptr};f.items[1].magazine=false;f.items[2].magazine=false;f.items[2].prefab.name="rifle";f.s.Stock(0);assert(f.s.m_Stock.Count()==1&&f.s.m_State==1);}
|
||||
{Fixture f;f.cargo.Finished=false;f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());f.s.Stock(89999);assert(!f.s.m_Stopped&&f.s.m_Stock.IsEmpty());f.s.Stock(90000);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
|
||||
{Fixture f;f.cargo.Finished=false;f.s.Stock(0);f.cargo.Finished=true;f.inventory.items={&f.items[0]};f.s.Stock(1000);assert(f.s.m_Stock.Count()==1&&!f.s.m_Stopped);}
|
||||
{Fixture f;f.inventory.items.Insert(&f.items[48]);f.s.Stock(0);assert(f.s.m_Stopped&&!f.Plan());f.Unchanged();}
|
||||
{Fixture f;for(int i=48;i<97;i++)f.inventory.items.Insert(&f.items[i]);f.s.Stock(0);assert(f.s.m_Stopped&&!f.Plan());f.Unchanged();}
|
||||
{Fixture f;for(auto& item:f.items)f.inventory.items.Insert(&item);f.s.Stock(0);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
|
||||
{Fixture f;f.vehicle.inventory=nullptr;f.s.Stock(0);assert(f.s.m_Stopped&&!f.Plan());}
|
||||
{Fixture f;f.convoy.Ready=false;f.s.Stock(0);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());}
|
||||
{Fixture f;f.receipt.Consumed=false;f.s.Stock(0);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
|
||||
// Supply weapons settle after crew deadline; matching ammo must be issued and settled before snapshot.
|
||||
{Fixture f;f.convoy.supply=true;f.convoy.MagazinesIssued=false;f.s.Stock(25000);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());
|
||||
f.s.Stock(31000);assert(!f.s.m_Stopped&&f.s.m_State==0);f.convoy.MagazinesIssued=true;f.cargo.Finished=false;
|
||||
f.s.Stock(32000);assert(f.s.m_State==0&&f.s.m_Stock.IsEmpty());f.cargo.Finished=true;
|
||||
f.items[0].prefab.name="NATO_556_STANAG";f.s.Stock(33000);assert(f.s.m_State==1&&!f.s.m_Stopped&&f.s.m_Stock.Contains(&f.items[0]));}
|
||||
// Earliest admission: producer may finish weapons only at original Deadline+30000.
|
||||
{Fixture f;f.convoy.supply=true;f.convoy.MagazinesIssued=false;f.cargo.Finished=false;f.s.Stock(0);
|
||||
f.s.Stock(59999);assert(!f.s.m_Stopped&&f.s.m_Stock.IsEmpty());
|
||||
f.convoy.MagazinesIssued=true;f.s.Stock(60000);assert(!f.s.m_Stopped&&f.s.m_State==0);
|
||||
f.cargo.Finished=true;f.items[0].prefab.name="NATO_556_STANAG";f.s.Stock(89000);
|
||||
assert(!f.s.m_Stopped&&f.s.m_State==1&&f.s.m_Stock.Contains(&f.items[0]));}
|
||||
// Missing first-phase submission and missing second phase are bounded by one immutable deadline.
|
||||
for(int phase=0;phase<2;phase++){Fixture f;f.convoy.supply=true;f.convoy.CargoIssued=phase!=0;f.convoy.MagazinesIssued=false;
|
||||
f.s.Stock(25000);f.s.Stock(114999);assert(!f.s.m_Stopped&&f.s.m_StockDeadline==115000&&f.s.m_Stock.IsEmpty());
|
||||
f.s.Stock(115000);assert(f.s.m_Stopped&&f.s.m_Stock.IsEmpty());}
|
||||
// Faction catalogs may yield 96 distinct magazine identities; weapons are excluded.
|
||||
{Fixture f;for(int i=48;i<96;i++)f.inventory.items.Insert(&f.items[i]);f.items[0].prefab.name="NATO_556_STANAG";
|
||||
f.items[96].magazine=false;f.inventory.items.Insert(&f.items[96]);f.s.Stock(0);
|
||||
assert(!f.s.m_Stopped&&f.s.m_State==1&&f.s.m_Stock.Count()==96&&f.s.m_Stock.Contains(&f.items[0])&&!f.s.m_Stock.Contains(&f.items[96]));}
|
||||
// Removed stock is unavailable; a different magazine added after capture does not replace its identity.
|
||||
{Fixture f;f.s.Stock(0);f.inventory.items={&f.items[48]};assert(!f.Plan());f.Unchanged();f.inventory.items.Insert(&f.items[0]);assert(f.Plan());assert(f.s.m_Stock.Count()==48&&!f.s.m_Stock.Contains(&f.items[48]));}
|
||||
{Fixture f;f.s.Stock(0);f.s.m_Spent=f.s.m_Stock;assert(!f.Plan());f.Unchanged();}
|
||||
|
||||
Reference in New Issue
Block a user