Files
EscapistsReintroduced/tests/test_spawn_scene_inventory.py
T

106 lines
8.6 KiB
Python

"""Execute production inventory manifest and client containment checks."""
from policy_harness import ROOT, body, run
inventory=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnSceneInventory.c'
scene=ROOT/'addons/core/Scripts/Game/ERR/00_Core/ERR_SpawnClientScene.c'
def cpp(s):
s=s.replace('protected ','').replace('static ','')
s=s.replace('ERR_SpawnSceneInventory.InStorage(', 'InStorage(')
s=s.replace('InventoryItemComponent item = InventoryItemComponent.Cast(entity.FindComponent(InventoryItemComponent));', 'Item* item = entity->item;')
s=s.replace('InventoryStorageSlot slot = item.GetParentSlot();', 'Slot* slot = item->slot;')
s=s.replace('BaseInventoryStorageComponent storage = slot.GetStorage();', 'Storage* storage = slot->storage;')
s=s.replace('storage.FindItemSlot(entity)', 'storage->FindItemSlot(entity)').replace('storage.Get(slot.GetID())', 'storage->Get(slot->id)').replace('storage.GetAll(visible)', 'storage->GetAll(visible)')
s=s.replace('out array<IEntity> ', 'Items& ').replace('array<IEntity> ', 'Items ')
s=s.replace('foreach (IEntity root : roots)', 'for (IEntity root : roots)').replace('foreach (IEntity item : contents)', 'for (IEntity item : contents)')
s=s.replace('InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(root.FindComponent(InventoryStorageManagerComponent));', 'Inventory* inventory = root->inventory;')
s=s.replace('InventoryStorageManagerComponent inventory = InventoryStorageManagerComponent.Cast(owner.GetEntity().FindComponent(InventoryStorageManagerComponent));', 'Inventory* inventory = owner->GetEntity()->inventory;')
s=s.replace('RplComponent owner = RplComponent.Cast(Replication.FindItem(ownerId));', 'RplComponent* owner = Replication::FindItem(ownerId);')
s=s.replace('RplComponent item = RplComponent.Cast(Replication.FindItem(m_ERRClientStockItems[i]));', 'RplComponent* item = Replication::FindItem(m_ERRClientStockItems[i]);')
s=s.replace('owner.GetEntity()', 'owner->GetEntity()').replace('item.GetEntity()', 'item->GetEntity()')
s=s.replace('RplId.Invalid()', '-1').replace('ERR_Inventory.Items(', 'InventoryItems(')
s=s.replace('Replication.IsServer()', 'Replication::server').replace('GetGame().GetWorld().GetWorldTime()', 'now')
s=s.replace('ERR_SpawnSceneInventory.Gather(', 'Gather(').replace('ERR_SpawnSceneInventory.Same(', 'Same(')
return s
methods='\n'.join(cpp(body(inventory,key)) for key in ('static bool InStorage(', 'static bool Gather(', 'static bool Same('))
methods+='\n'+cpp(body(scene,'protected bool ERR_ClientStockReady('))
methods+='\n'+cpp(body(scene,'protected void ERR_RefreshSceneInventory('))
methods+='\n'+cpp(body(scene,'bool ERR_TakeScenePreload('))
run(r'''
#include <cassert>
#include <vector>
#include <algorithm>
#include <map>
template<class T>struct List:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;
int Count()const{return this->size();}void Insert(T x){this->push_back(x);}bool Contains(T x)const{return std::find(this->begin(),this->end(),x)!=this->end();}
int Find(T x)const{auto i=std::find(this->begin(),this->end(),x);return i==this->end()?-1:i-this->begin();}};
struct Entity;using IEntity=Entity*;using Items=List<IEntity>;using RplId=int;
struct Storage;struct Slot{Storage* storage=nullptr;int id=0;};struct Item{Slot* slot=nullptr;};
struct Inventory{Items items;};struct Entity{Inventory* inventory=nullptr;Item* item=nullptr;};
struct Storage{Items visible;IEntity stored=nullptr;Slot* slot=nullptr;
Slot* FindItemSlot(IEntity e){return stored==e?slot:nullptr;}IEntity Get(int id){return slot&&slot->id==id?stored:nullptr;}void GetAll(Items& out){out=visible;}};
void InventoryItems(Inventory* inv,Items& items){items={};for(auto x:inv->items)if(x&&!items.Contains(x))items.Insert(x);}
struct RplComponent{IEntity entity=nullptr;IEntity GetEntity(){return entity;}};
namespace Replication{bool server=true;std::map<int,RplComponent*> nodes;RplComponent* FindItem(int id){auto it=nodes.find(id);return it==nodes.end()?nullptr:it->second;}}
template<typename... T>void PrintFormat(const char*,T...){}
struct Probe{
List<int> m_ERRClientStockOwners,m_ERRClientStockItems;
bool m_ERRScenePending=true,m_ERRSceneTimedOut=false,m_ERRSceneAcknowledged=false,m_ERRScenePreloadRequired=true,preloading=false;
int publishes=0,waits=0,m_ERRSceneToken=7;
float now=0,m_ERRSceneInventoryPoll=0,m_ERRSceneDeadline=60000,m_ERRSceneStartedAt=0;
Items m_ERRSceneRoots,m_ERRSceneStockOwners,m_ERRSceneStockItems;
void ERR_ReportStockWait(int,int,int){waits++;}int GetPlayerId(){return 1;}
bool IsPreloading(){return preloading;}bool ERR_SpawnSceneReady(){return m_ERRSceneAcknowledged&&!m_ERRSceneTimedOut;}
void ERR_PublishSpawnScene(Items owners,Items items,bool valid){publishes++;m_ERRSceneToken++;m_ERRSceneAcknowledged=false;m_ERRSceneStockOwners=owners;m_ERRSceneStockItems=items;m_ERRSceneTimedOut=!valid;}
'''+methods+r'''
};
int main(){
Inventory inv;Entity crate{&inv},gun,mag,map;inv.items={&gun,&mag,&map,&gun,nullptr};
Probe p;Items owners,items;assert(p.Gather({&crate,&crate,nullptr},owners,items)&&items.Count()==3);
assert((items==Items{&gun,&mag,&map})&&(owners==Items{&crate,&crate,&crate}));
assert(p.Same(owners,items,{&crate,&crate,&crate},{&map,&mag,&gun}));
Entity other;assert(!p.Same(owners,items,{&other,&crate,&crate},items));
assert(!p.Same(owners,items,{&crate,&crate},{&gun,&mag}));
std::vector<Entity> many(257);Inventory full;for(auto& e:many)full.items.Insert(&e);Entity huge{&full};
assert(!p.Gather({&huge},owners,items)&&items.Count()==256&&owners.Count()==256);
// Receiving every replication entity is insufficient: inventory links may
// arrive later, and a wrong crate with the same item count is insufficient.
RplComponent cr{&crate},gr{&gun},mr{&mag},pr{&map};
Replication::nodes={{10,&cr},{11,&gr},{12,&mr},{13,&pr}};
p.m_ERRClientStockOwners={10,10,10};p.m_ERRClientStockItems={11,12,13};
inv.items={};for(int i=0;i<20;i++)assert(!p.ERR_ClientStockReady());
inv.items={&gun,&mag};assert(!p.ERR_ClientStockReady());
inv.items={&gun,&mag,&other};assert(!p.ERR_ClientStockReady());
inv.items={&gun,&mag,&map};assert(!p.ERR_ClientStockReady());
Storage gs,ms,ps;Slot gslot{&gs,0},mslot{&ms,1},pslot{&ps,2};Item gi{&gslot},mi{&mslot},pi{&pslot};
gun.item=&gi;mag.item=&mi;map.item=&pi;
gs.stored=&gun;gs.slot=&gslot;gs.visible={&gun};ms.stored=&mag;ms.slot=&mslot;ms.visible={&mag};ps.stored=&map;ps.slot=&pslot;ps.visible={&map};
assert(p.ERR_ClientStockReady());
// Manager still contains the gun, but the actual crate UI cannot read it.
gs.visible={};assert(!p.ERR_ClientStockReady());gs.visible={&gun};
gs.stored=&other;assert(!p.ERR_ClientStockReady());gs.stored=&gun;
gi.slot=nullptr;assert(!p.ERR_ClientStockReady());gi.slot=&gslot;
gslot.storage=nullptr;assert(!p.ERR_ClientStockReady());gslot.storage=&gs;
gs.slot=&mslot;assert(!p.ERR_ClientStockReady());gs.slot=&gslot;
assert(p.ERR_ClientStockReady());
cr.entity=nullptr;assert(!p.ERR_ClientStockReady());cr.entity=&crate;
crate.inventory=nullptr;assert(!p.ERR_ClientStockReady());crate.inventory=&inv;
mr.entity=nullptr;assert(!p.ERR_ClientStockReady());mr.entity=&mag;
p.m_ERRClientStockOwners={10};assert(!p.ERR_ClientStockReady());
// Refresh a consumed/relocated stock snapshot without extending its timeout.
p.m_ERRSceneRoots={&crate};assert(p.Gather(p.m_ERRSceneRoots,p.m_ERRSceneStockOwners,p.m_ERRSceneStockItems));
p.ERR_RefreshSceneInventory();assert(p.publishes==0);
p.m_ERRSceneAcknowledged=true;inv.items={&gun};p.now=499;p.ERR_RefreshSceneInventory();assert(p.publishes==0);
p.now=500;p.ERR_RefreshSceneInventory();assert(p.publishes==1&&p.m_ERRSceneToken==8&&!p.m_ERRSceneAcknowledged&&p.m_ERRSceneDeadline==60000);
assert((p.m_ERRSceneStockItems==Items{&gun}));
p.m_ERRSceneStockItems={&gun,&mag};p.now=1000;Replication::server=false;p.ERR_RefreshSceneInventory();assert(p.publishes==1);Replication::server=true;
p.m_ERRSceneTimedOut=true;p.ERR_RefreshSceneInventory();assert(p.publishes==1);p.m_ERRSceneTimedOut=false;
p.m_ERRScenePending=false;p.ERR_RefreshSceneInventory();assert(p.publishes==1);
// Preload cannot start before inventory acknowledgement, starts once, and
// waits for an earlier native pass before consuming a refreshed request.
assert(!p.ERR_TakeScenePreload());p.m_ERRSceneAcknowledged=true;
assert(p.ERR_TakeScenePreload()&&!p.ERR_TakeScenePreload());
p.m_ERRScenePreloadRequired=true;p.preloading=true;assert(!p.ERR_TakeScenePreload());
p.preloading=false;assert(p.ERR_TakeScenePreload());
}
''')
print('PASS: actual inventory manifest, delayed/missing/wrong-owner item links, finite cap, consumed-stock refresh and preload ordering')