Build realistic roadblocks with staggered barriers and guarded positions

This commit is contained in:
2026-10-15 12:39:04 +02:00
parent 7b13231f29
commit 3b920e0715
6 changed files with 267 additions and 40 deletions
+15 -13
View File
@@ -5,8 +5,8 @@ p=ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_Roadblocks.c'
s='\n'.join(body(p,signature) for signature in ('protected ERR_PrepareResult Finish(', 'ERR_PrepareResult Prepare(', 'protected void SearchStep(', 'protected void BackgroundStep()', 'void Stop()'))
s=re.sub(r'#ifdef WORKBENCH.*?#endif','',s,flags=re.S)
s=s.replace('protected ', '').replace('ERR_ScenarioDefinition definition', 'Definition* definition')
s=s.replace('array<vector> positions = {}, covers = {};', 'Array<int> positions, covers;')
s=s.replace('GetGame().GetCallqueue().CallLater(BackgroundStep, 500, true)', 'queue.Schedule(500)').replace('GetGame().GetCallqueue().Remove(BackgroundStep)', 'queue.Remove()')
s=s.replace('array<vector> positions = {};', 'Array<int> positions;').replace('array<ref ERR_RoadblockProp> covers = {};', 'Array<int> covers;')
s=s.replace('GetGame().GetCallqueue().CallLater(BackgroundStep, 200, true)', 'queue.Schedule(200)').replace('GetGame().GetCallqueue().Remove(BackgroundStep)', 'queue.Remove()')
s=s.replace('GetGame().GetWorldEntity().GetWorldBounds(m_Low, m_High)', 'Bounds(m_Low, m_High)').replace('GetGame().GetPlayerManager().GetPlayerCount()', 'players')
s=s.replace('m_Mode.Settings().Get("roadblockCount")', 'm_Mode->target').replace('m_Mode.Forces().Count()', 'm_Mode->ledger')
s=s.replace('m_Mode.', 'm_Mode->').replace('System.GetTickCount()', 'Now()').replace('Replication.IsServer()', 'server').replace('ERR_GameMode.Get()', 'current')
@@ -27,7 +27,7 @@ int clockMs=0,players=1;bool server=true;
int Now(){return clockMs;}bool GetGame(){return true;}void Bounds(int&,int&){}bool ValidBounds(int,int){return true;}
template<class...T>void PrintFormat(T...){}void Event(string,string){}
template<class T>struct Array:std::vector<T>{int Count(){return this->size();}void Clear(){this->clear();}};
struct Queue{int scheduled=0,removed=0;bool active=false;void Schedule(int ms){assert(ms==500);scheduled++;active=true;}void Remove(){removed++;active=false;}};
struct Queue{int scheduled=0,removed=0;bool active=false;void Schedule(int ms){assert(ms==200);scheduled++;active=true;}void Remove(){removed++;active=false;}};
struct Mode{int target=30,ledger=0,phase=Phase::PRISON;bool active=true;int Phase(){return phase;}bool Active(){return active;}};Mode* current=nullptr;
struct Definition{};
struct Mining{bool stopped=false;void Stop(){stopped=true;}};
@@ -99,9 +99,10 @@ print('PASS: actual roadblock scheduler yields initial deadline/attempt cap to b
# Run actual staged physical creation and rollback, with native entities doubled.
s='\n'.join(body(p,sig) for sig in ('protected bool SpawnPost(', 'protected void CancelPost()', 'protected void BuildPost()'))
s=s.replace('protected ', '').replace('array<vector> positions','Array<int>& positions').replace('array<vector> covers','Array<int>& covers')
s=s.replace('protected ', '').replace('array<vector> positions','Array<int>& positions').replace('array<ref ERR_RoadblockProp> covers','Array<Prop>& covers').replace('ERR_RoadblockProp prop', 'Prop prop')
s=s.replace('SCR_AIGroup.Cast','Node::Cast').replace('AIWaypoint.Cast','Node::Cast').replace('IEntity actor','Node* actor').replace('IEntity cover','Node* cover')
s=s.replace('foreach (IEntity ownedEntity : m_BuildSpawned)','for (Node* ownedEntity : m_BuildSpawned)').replace('vector point','int point').replace('vector coverPosition','int coverPosition')
s=s.replace('foreach (ERR_RoadblockProp plannedProp : covers)', 'for (Prop plannedProp : covers)')
s=s.replace('m_Forward.VectorToAngles()', '0').replace('m_Positions.Count().ToString()', 'std::to_string(m_Positions.Count())')
s=s.replace('GetGame().GetWorld().GetWorldTime()', 'Now()').replace('System.GetTickCount()', 'Now()').replace('null','nullptr')
s=s.replace('m_BuildGroup.', 'm_BuildGroup->').replace('m_BuildHold.', 'm_BuildHold->')
@@ -122,31 +123,32 @@ struct Ledger{int count=0,registered=0;bool accept=true;int Count(){return count
struct Mode{Ledger ledger;int owned=0,groups=0;Ledger& Forces(){return ledger;}int EnemyFaction(){return 1;}void Own(Node*n){assert(n);owned++;}void RegisterGroup(Node*){groups++;}};
struct Placement{bool clear=true;bool Clear(int,void*){return clear;}};
struct Mining{int registered=0;void Register(Node*,Node*,int){registered++;}};
struct Prop{string Prefab="cover";int Position=5,Angles=0;};
struct Build{
Mode m_Mode;Placement m_Placement;Mining m_Mining;Node* m_BuildGroup=nullptr;Node* m_BuildHold=nullptr;
Array<int> m_BuildPositions,m_BuildCovers,m_Positions;Array<Node*>m_BuildSpawned,m_Groups;Array<string>m_Roster={"one","two"};
Array<int> m_BuildPositions,m_Positions;Array<Prop>m_BuildCovers;Array<Node*>m_BuildSpawned,m_Groups;Array<string>m_Roster={"one","two"};
int m_BuildActors=0,m_BuildCoverCount=0,m_Centre=10,m_Right=0,m_NextSearch=0,rolledBack=0;string COVER="cover";bool playersClear=true,coversClear=true;
bool PlayersClear(){return playersClear;}bool CoverClear(int){return coversClear;}
bool PlayersClear(){return playersClear;}bool CoverClear(Prop){return coversClear;}
void Rollback(Array<Node*>&entities,Node*group){rolledBack+=entities.Count()+(group?1:0);}
'''+s+r'''
};
int main(){Array<int> positions={1,2,3,4},covers={5,6};
int main(){Array<int> positions={1,2,3,4};Array<Prop>covers(10);
{Build b;int start=ERR_Actors::calls;assert(b.SpawnPost(positions,covers));assert(ERR_Actors::calls==start+1);
for(int piece=0;piece<7;piece++){int calls=ERR_Actors::calls;b.BuildPost();assert(ERR_Actors::calls==calls+1&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);}
b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==1&&b.m_Groups.Count()==1&&b.m_Mode.owned==8&&b.m_Mode.groups==1&&b.m_Mining.registered==1&&b.m_NextSearch==2100);
for(int piece=0;piece<15;piece++){int calls=ERR_Actors::calls;b.BuildPost();assert(ERR_Actors::calls==calls+1&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);}
b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==1&&b.m_Groups.Count()==1&&b.m_Mode.owned==16&&b.m_Mode.groups==1&&b.m_Mining.registered==1&&b.m_NextSearch==2100);
}
// Player approaches at any stage: no registered post and every owned piece removed.
for(int stage=0;stage<=7;stage++){Build b;assert(b.SpawnPost(positions,covers));for(int i=0;i<stage;i++)b.BuildPost();b.playersClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0&&b.rolledBack==stage+1);}
for(int stage=0;stage<=15;stage++){Build b;assert(b.SpawnPost(positions,covers));for(int i=0;i<stage;i++)b.BuildPost();b.playersClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0&&b.rolledBack==stage+1);}
// Any native entity creation failure rolls back rather than consuming a post.
for(int stage=0;stage<8;stage++){Build b;ERR_Actors::failAt=ERR_Actors::calls+stage+1;bool begun=b.SpawnPost(positions,covers);
for(int stage=0;stage<16;stage++){Build b;ERR_Actors::failAt=ERR_Actors::calls+stage+1;bool begun=b.SpawnPost(positions,covers);
if(stage==0)assert(!begun);else{for(int i=0;i<stage;i++)b.BuildPost();assert(b.rolledBack==stage);}
assert(!b.m_BuildGroup&&b.m_Positions.Count()==0&&b.m_Mode.owned==0);ERR_Actors::failAt=0;
}
{Build b;b.SpawnPost(positions,covers);b.m_Placement.clear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==1);}
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<4;i++)b.BuildPost();b.coversClear=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==5);}
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<7;i++)b.BuildPost();b.m_Mode.ledger.accept=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==8&&b.m_Positions.Count()==0);}
{Build b;b.SpawnPost(positions,covers);for(int i=0;i<15;i++)b.BuildPost();b.m_Mode.ledger.accept=false;b.BuildPost();assert(!b.m_BuildGroup&&b.rolledBack==16&&b.m_Positions.Count()==0);}
{Build b;b.SpawnPost(positions,covers);b.BuildPost();b.CancelPost();assert(!b.m_BuildGroup&&b.rolledBack==2&&b.m_Mode.owned==0);}
for(auto*n:ERR_Actors::all)delete n;
}
''')
print('PASS: actual staged post creation spawns at most one entity per callback, commits only all four guards/two covers/waypoint, and rolls back player approach, obstructions, spawn/ledger failure and cancellation')
print('PASS: actual staged post creation spawns at most one entity per callback, commits only all four guards/ten props/waypoint, and rolls back player approach, obstructions, spawn/ledger failure and cancellation')