Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
241 lines
34 KiB
Python
241 lines
34 KiB
Python
"""Execute real engineer ownership, callback and Tick bodies; native boundaries are doubles.
|
|
No movement, Enforce compilation, pressure detonation or multiplayer proof.
|
|
"""
|
|
import os,re
|
|
from pathlib import Path
|
|
from policy_harness import ROOT,body,run
|
|
P=Path(os.environ.get('ERR_ENGINEER_MINE_SOURCE',ROOT/'addons/core/Scripts/Game/ERR/10_AI/ERR_EngineerMine.c'))
|
|
def cpp(s):
|
|
s=s.replace('protected override ','').replace('protected ','').replace('null','nullptr').replace('ref ','')
|
|
s=s.replace('array<','Array<').replace(' = {};',';').replace('foreach (','for (')
|
|
s=s.replace('array<IEntity> attempted','Array<IEntity>* attempted')
|
|
s=s.replace('Array<vector> priorSites','Array<vector>* priorSites').replace('priorSites.','priorSites->').replace(': priorSites)',': *priorSites)')
|
|
s=s.replace(', m_PriorSites)',', &m_PriorSites)').replace('Math.Clamp','Math::Clamp')
|
|
for typ in ['ERR_ForceRecord','SCR_AIGroupUtilityComponent','ERR_GameMode','ERR_HQUnit','ERR_HQTask','SCR_ChimeraAIAgent','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','Faction','Physics']:
|
|
s=re.sub(r'\b'+typ+r' (\w+)',typ+r'* \1',s)
|
|
for ns in ['ERR_ForceRules','Replication','ERR_GameMode','ERR_HunterCombat','ERR_Actors','CompartmentAccessComponent','SCR_FactionManager','SCR_ChimeraAIAgent','SCR_AIUtils','ERR_EnemyLoadoutCatalog','ERR_Inventory','ERR_InfantryAmmoTransfer','InventoryItemComponent','DamageManagerComponent','FactionAffiliationComponent','ChimeraCharacter','Vehicle','SCR_InventoryStorageManagerComponent','SCR_PressureTriggerComponent','ERR_EngineerMineRules','ERR_HQ','vector']:
|
|
s=s.replace(ns+'.',ns+'::')
|
|
for obj in ['groupUtility','m_Mode','m_Unit','m_Group','m_Task','m_Assignment','m_Record','m_Actor','m_Mine','m_Trigger','m_Inventory','m_Utility','m_Action','m_Removal','m_Attempted','mode','unit','task','actor','mine','agent','inventory','trigger','item','entity','physics','damage','affiliation','faction']:
|
|
s=re.sub(r'\b'+obj+r'\.',obj+'->',s)
|
|
for field in ['StatusSampler','InfantryCombat','m_UtilityComponent']:
|
|
s=s.replace(field+'.',field+'->')
|
|
s=s.replace('InfantryCombat->Held','InfantryCombat.Held')
|
|
s=s.replace('GetPrefabData().','GetPrefabData()->').replace('EnemyFaction().','EnemyFaction()->').replace('Forces().','Forces()->').replace('MineIntel().','MineIntel()->')
|
|
s=s.replace('GetGame().','GetGame()->').replace('GetWorld().','GetWorld()->')
|
|
s=re.sub(r'FindComponent\((\w+)\)',r'FindComponent("\1")',s)
|
|
s=s.replace('CheckFriendly, queryFlags: EQueryEntitiesFlags.ALL','[this](IEntity entity){return CheckFriendly(entity);}, 1')
|
|
s=s.replace('ERR_EngineerMine(Array<IEntity> attempted)','ERR_EngineerMine(Array<IEntity>* attempted)')
|
|
s=s.replace('Faction* faction;','Faction* faction=nullptr;').replace('Array<AIActionBase>', 'Array<AIActionBase*>').replace('void ERR_EngineerMine(', 'ERR_EngineerMine(')
|
|
return s
|
|
policies='\n'.join(body(P,x) for x in ['static bool CorridorClear(','static bool WithinWorkArea(','static bool Within(','static float PrepareSince(','static bool Prepared(','static bool MayArm('])
|
|
callback='\n'.join(body(P,x) for x in ['protected void Finish(','protected override void OnComplete(','protected override void OnAllComplete(','protected override void OnFailed(','protected override void OnAllFailed('])
|
|
methods='\n'.join(body(P,x) for x in ['void ERR_EngineerMine(','bool Finished(','bool BlocksNextPost(','protected bool Refuse(','protected bool Quiet(','protected bool OrderValid(', 'protected void OnArmed(', 'protected bool Owned(','protected bool MineValid(','protected bool Carried(','protected bool Grounded(','protected bool Placed(','protected void ClearAction(','protected void Move(','protected void WaitAtMine(','protected bool CheckFriendly(','protected bool SelectActor(','bool Begin(','void Stop(','void Tick('])
|
|
run(r'''
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <algorithm>
|
|
#include <functional>
|
|
struct string:std::string {using std::string::string;using std::string::operator=;bool IsEmpty(){return empty();}};
|
|
struct vector {float x=0,y=0,z=0;float operator[](int i){return i==0?x:i==1?y:z;}float LengthSq(){return x*x+y*y+z*z;}
|
|
static float Distance(vector a,vector b){return std::sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y)+(a.z-b.z)*(a.z-b.z));}
|
|
static float DistanceXZ(vector a,vector b){a.y=b.y=0;return Distance(a,b);}};
|
|
template<class T>struct Array:std::vector<T>{using std::vector<T>::vector;using std::vector<T>::operator=;int Count(){return this->size();}bool Contains(T t){return std::find(this->begin(),this->end(),t)!=this->end();}void Insert(T t){this->push_back(t);}};
|
|
namespace Math{float Clamp(float v,float a,float b){return std::max(a,std::min(v,b));}}
|
|
struct Entity;using IEntity=Entity*;struct SCR_AIGroup;struct SCR_ChimeraAIAgent;struct SCR_InventoryStorageManagerComponent;struct ERR_EngineerMineRemoval;
|
|
struct Faction {bool enemy=false;bool IsFactionEnemy(Faction* f){return f&&f->enemy;}};
|
|
struct Physics {vector velocity;vector GetVelocity(){return velocity;}};
|
|
struct Prefab {string name="PMN4";string GetPrefabName(){return name;}};
|
|
struct InventoryItemComponent {void* slot=nullptr;void* GetParentSlot(){return slot;}static InventoryItemComponent* Cast(void* p){return (InventoryItemComponent*)p;}};
|
|
struct DamageManagerComponent {bool destroyed=false;bool IsDestroyed(){return destroyed;}static DamageManagerComponent* Cast(void* p){return (DamageManagerComponent*)p;}};
|
|
struct FactionAffiliationComponent {Faction* faction=nullptr;Faction* GetAffiliatedFaction(){return faction;}static FactionAffiliationComponent* Cast(void* p){return (FactionAffiliationComponent*)p;}};
|
|
struct SCR_PressureTriggerComponent {bool active=false,byDefault=false,allow=true;int calls=0,users=0;IEntity user=nullptr;
|
|
bool IsActivated(){return active;}bool IsActivatedByDefault(){return byDefault;}void SetUser(IEntity e){users++;user=e;}void ActivateTrigger(){calls++;active=allow;}static SCR_PressureTriggerComponent* Cast(void* p){return (SCR_PressureTriggerComponent*)p;}};
|
|
struct Entity {vector position;bool alive=true,player=false,character=true,vehicle=false,stored=false;int moves=0;IEntity parent=nullptr,inVehicle=nullptr,storageOwner=nullptr;
|
|
Prefab* prefab=nullptr;Physics* physics=nullptr;InventoryItemComponent* item=nullptr;SCR_PressureTriggerComponent* trigger=nullptr;SCR_InventoryStorageManagerComponent* inventory=nullptr;DamageManagerComponent* damage=nullptr;FactionAffiliationComponent* affiliation=nullptr;Faction* faction=nullptr;SCR_AIGroup* group=nullptr;SCR_ChimeraAIAgent* agent=nullptr;
|
|
vector GetOrigin(){return position;}void SetOrigin(vector p){position=p;moves++;}IEntity GetParent(){return parent;}Prefab* GetPrefabData(){return prefab;}Physics* GetPhysics(){return physics;}
|
|
void* FindComponent(std::string type){if(type=="InventoryItemComponent")return item;if(type=="SCR_PressureTriggerComponent")return trigger;if(type=="SCR_InventoryStorageManagerComponent")return inventory;if(type=="DamageManagerComponent")return damage;if(type=="FactionAffiliationComponent")return affiliation;return nullptr;}
|
|
};
|
|
struct ChimeraCharacter {static bool Cast(IEntity e){return e&&e->character;}};struct Vehicle {static bool Cast(IEntity e){return e&&e->vehicle;}};
|
|
// Native group movement envelope boundary, not navigation/pathfinding.
|
|
struct AIWaypoint {virtual ~AIWaypoint()=default;};
|
|
struct SCR_DefendWaypoint:AIWaypoint {vector origin;float radius=30;vector GetOrigin(){return origin;}float GetCompletionRadius(){return radius;}static SCR_DefendWaypoint* Cast(AIWaypoint* p){return dynamic_cast<SCR_DefendWaypoint*>(p);}};
|
|
struct SCR_AIGroupUtilityComponent;
|
|
struct SCR_AIGroup {bool slave=false;int players=0;Faction* faction=nullptr;SCR_AIGroupUtilityComponent* utility=nullptr;AIWaypoint* waypoint=nullptr;vector center;
|
|
bool IsSlave(){return slave;}int GetPlayerCount(bool){return players;}Faction* GetFaction(){return faction;}SCR_AIGroupUtilityComponent* GetGroupUtilityComponent(){return utility;}AIWaypoint* GetCurrentWaypoint(){return waypoint;}vector GetCenterOfMass(){return center;}};
|
|
struct SCR_AIGroupUtilityComponent {SCR_AIGroup* m_Owner=nullptr;float m_fMaxAutonomousDistance=100;
|
|
// Matches installed SCR_AIGroupUtilityComponent.IsPositionAllowed semantics.
|
|
bool IsPositionAllowed(vector pos){auto wp=m_Owner->GetCurrentWaypoint();auto defendWp=SCR_DefendWaypoint::Cast(wp);if(defendWp)return vector::Distance(defendWp->GetOrigin(),pos)<defendWp->GetCompletionRadius();return vector::Distance(m_Owner->GetCenterOfMass(),pos)<m_fMaxAutonomousDistance;}
|
|
};
|
|
struct SCR_AIBehaviorBase {int completed=0;float priority=0;void Complete(){completed++;}void SetPriority(float p){priority=p;}};using AIActionBase=SCR_AIBehaviorBase;
|
|
struct SCR_AIUtilityComponent {bool m_AIInfo=true;Array<AIActionBase*> actions;int additions=0;void GetActions(Array<AIActionBase*>& out){out=actions;}void AddAction(AIActionBase* a){additions++;actions.Insert(a);}};
|
|
struct SCR_AIMoveIndividuallyBehavior:SCR_AIBehaviorBase {SCR_AIMoveIndividuallyBehavior(SCR_AIUtilityComponent*,void*,vector,float,float,void*,float){}};
|
|
struct SCR_AIWaitBehavior:SCR_AIBehaviorBase {SCR_AIWaitBehavior(SCR_AIUtilityComponent*,void*){}};
|
|
struct SCR_ChimeraAIAgent {SCR_AIUtilityComponent* m_UtilityComponent=nullptr;static SCR_ChimeraAIAgent* Cast(SCR_ChimeraAIAgent* p){return p;}};
|
|
struct SCR_InventoryStorageManagerComponent {Array<IEntity> items;int calls=0,inlineResult=0;bool accepted=true;ERR_EngineerMineRemoval* callback=nullptr;
|
|
bool TryRemoveItemFromInventory(IEntity,void*,ERR_EngineerMineRemoval*);static SCR_InventoryStorageManagerComponent* Cast(void* p){return (SCR_InventoryStorageManagerComponent*)p;}};
|
|
struct Sampler {Array<IEntity> members;bool owns=true,attribute=true;int InitialStrength(){return members.Count();}bool OwnsRoster(SCR_AIGroup*){return owns;}bool CanAttribute(int slot,IEntity e,bool){return attribute&&slot>=0&&slot<members.Count()&&members[slot]==e;}IEntity InitialMember(int slot){return members[slot];}};
|
|
struct ERR_HQTask {bool RoadIntercept=true,ReleaseOnly=false,investigation=false;string SecuritySite;vector Destination;float ExpiresAt=999999;bool Investigation(){return investigation;}};
|
|
struct ERR_HQReport{};struct ERR_ForceRecord {SCR_AIGroup* Group=nullptr;bool Physical=true,Eliminated=false;string SourceId="original";int InitialStrength=1;};
|
|
struct Ledger {ERR_ForceRecord* record=nullptr;ERR_ForceRecord* FindGroup(SCR_AIGroup*){return record;}};
|
|
struct ERR_HQUnit {SCR_AIGroup* Group=nullptr;ERR_HQTask* Task=nullptr;ERR_HQReport* Assignment=nullptr;ERR_HQTask* PendingTask=nullptr;ERR_HQReport* PendingOrder=nullptr;Sampler* StatusSampler=nullptr;struct{bool Held=false;}InfantryCombat;bool ReservedForSupport=false;int LogisticsReservation=0;string ForceSourceId="original";};
|
|
struct MineIntel {int candidates=0,seals=0;IEntity capturedActor=nullptr,sealedMine=nullptr;vector position;Array<IEntity> available,captured,sealed;
|
|
void Candidates(IEntity actor,Array<IEntity>&out){candidates++;capturedActor=actor;out=available;captured=out;}
|
|
void Seal(IEntity actor,IEntity mine,vector p,Array<IEntity>&folders){assert(actor==capturedActor&&mine&&mine->trigger&&mine->trigger->active);seals++;sealedMine=mine;position=p;sealed=folders;assert(folders==captured);}
|
|
};
|
|
struct ERR_GameMode {struct MineIntel mineIntel;bool haveMineIntel=true;struct MineIntel* MineIntel(){return haveMineIntel?&mineIntel:nullptr;}static inline ERR_GameMode* current=nullptr;bool active=true;Faction* faction=nullptr;Ledger* ledger=nullptr;static ERR_GameMode* Get(){return current;}bool Active(){return active;}Faction* EnemyFaction(){return faction;}Ledger* Forces(){return ledger;}};
|
|
namespace ERR_ForceRules {bool Finite(float f){return std::isfinite(f);}bool FiniteVector(vector v){return Finite(v.x)&&Finite(v.y)&&Finite(v.z);}}
|
|
namespace Replication {bool server=true;bool IsServer(){return server;}}
|
|
namespace ERR_HunterCombat {bool engaged=false;bool Active(SCR_AIGroup*){return engaged;}}
|
|
namespace ERR_Actors {bool Alive(IEntity a){return a&&a->alive;}bool CanFight(IEntity a){return a&&a->alive;}bool Player(IEntity a){return a&&a->player;}SCR_AIGroup* Group(IEntity a){return a->group;}}
|
|
namespace CompartmentAccessComponent {IEntity GetVehicleIn(IEntity a){return a->inVehicle;}}
|
|
namespace SCR_FactionManager {Faction* SGetFaction(IEntity a){return a->faction;}}
|
|
namespace SCR_AIUtils {SCR_ChimeraAIAgent* GetAIAgent(IEntity a){return a->agent;}}
|
|
namespace ERR_EnemyLoadoutCatalog {const string MINE="PMN4";}
|
|
namespace ERR_Inventory {void Items(SCR_InventoryStorageManagerComponent* inv,Array<IEntity>& items){items=inv->items;}}
|
|
namespace ERR_InfantryAmmoTransfer {bool StoredUnder(IEntity mine,IEntity actor){return mine->stored&&mine->storageOwner==actor;}}
|
|
namespace ERR_HQ {int events=0;void Event(string,string){events++;}}
|
|
struct World {Array<IEntity> query;int queries=0;void QueryEntitiesBySphere(vector,float,std::function<bool(IEntity)> fn,int){queries++;for(auto e:query)if(!fn(e))break;}};
|
|
struct Game {World world;World* GetWorld(){return &world;}} game;Game* currentGame=&game;Game* GetGame(){return currentGame;}
|
|
struct ERR_EngineerMineRules {
|
|
'''+cpp(policies)+r'''};
|
|
struct ERR_EngineerMineRemoval {int Result=0;
|
|
'''+cpp(callback)+r'''};
|
|
bool SCR_InventoryStorageManagerComponent::TryRemoveItemFromInventory(IEntity,void*,ERR_EngineerMineRemoval* cb){calls++;callback=cb;if(inlineResult>0)cb->OnComplete();if(inlineResult<0)cb->OnFailed();return accepted;}
|
|
struct ERR_EngineerMine {
|
|
ERR_GameMode* m_Mode=nullptr;ERR_HQUnit* m_Unit=nullptr;SCR_AIGroup* m_Group=nullptr;ERR_HQTask* m_Task=nullptr;ERR_HQReport* m_Assignment=nullptr;ERR_ForceRecord* m_Record=nullptr;string m_SourceId;int m_Strength=0;IEntity m_Actor=nullptr;int m_Slot=0;IEntity m_Mine=nullptr;SCR_PressureTriggerComponent* m_Trigger=nullptr;SCR_InventoryStorageManagerComponent* m_Inventory=nullptr;SCR_AIUtilityComponent* m_Utility=nullptr;SCR_AIBehaviorBase* m_Action=nullptr;ERR_EngineerMineRemoval* m_Removal=nullptr;Array<IEntity>* m_Attempted=nullptr;Array<IEntity> m_IntelFolders;Array<vector> m_PriorSites;vector m_Position,m_Rally;float m_Started=-1,m_LastTick=-1,m_PreparedAt=-1,m_NextClearance=0,m_NextArmLog=0;int m_State=0,m_Checked=0;bool m_FriendlyNear=false,m_Begun=false,m_Stopped=false;
|
|
'''+cpp(methods)+r'''};
|
|
struct Fixture {Faction faction,hostile{true};SCR_AIGroup group;SCR_AIGroupUtilityComponent groupUtility;SCR_AIUtilityComponent utility;SCR_ChimeraAIAgent agent;SCR_InventoryStorageManagerComponent inventory;Entity actor,mine;Prefab prefab;Physics physics;InventoryItemComponent item;SCR_PressureTriggerComponent trigger;Sampler roster;ERR_HQTask task;ERR_HQReport report;ERR_ForceRecord record;Ledger ledger;ERR_GameMode mode;ERR_HQUnit unit;Array<IEntity> attempted;ERR_EngineerMine controller{&attempted};
|
|
Fixture(){ERR_GameMode::current=&mode;Replication::server=true;ERR_HunterCombat::engaged=false;currentGame=&game;game.world.query.clear();game.world.queries=0;
|
|
group.faction=&faction;group.utility=&groupUtility;groupUtility.m_Owner=&group;actor.group=&group;actor.faction=&faction;actor.agent=&agent;actor.inventory=&inventory;agent.m_UtilityComponent=&utility;
|
|
mine.prefab=&prefab;mine.physics=&physics;mine.item=&item;mine.trigger=&trigger;mine.stored=true;mine.storageOwner=&actor;item.slot=&inventory;inventory.items.Insert(&mine);roster.members.Insert(&actor);
|
|
record.Group=&group;ledger.record=&record;mode.faction=&faction;mode.ledger=&ledger;unit.Group=&group;unit.Task=&task;unit.Assignment=&report;unit.StatusSampler=&roster;}
|
|
void Begin(){assert(controller.Begin(&mode,&unit,&task,{30,0,0},1000));assert(attempted.Count()==1&&utility.additions==1&&mode.mineIntel.candidates==1&&mode.mineIntel.capturedActor==&actor&&mode.mineIntel.seals==0);}
|
|
void Prepare(){Begin();actor.position={30,0,0};controller.Tick(2000);assert(controller.m_State==1&&utility.additions==2);controller.Tick(12000);assert(inventory.calls==1);assert(controller.m_State==2||controller.Finished());}
|
|
void Drop(){mine.position=actor.position;mine.stored=false;mine.storageOwner=nullptr;item.slot=nullptr;inventory.items.clear();}
|
|
void Withdraw(){Prepare();Drop();inventory.callback->OnComplete();controller.Tick(12001);assert(controller.m_State==3&&mine.moves==1&&utility.additions==3);}
|
|
};
|
|
int main(){
|
|
// History geometry uses bounded detached coordinates, not live mine entities.
|
|
Array<vector> hazards;hazards.Insert({20,0,20});
|
|
assert(!ERR_EngineerMineRules::CorridorClear({0,0,0},{40,0,0},&hazards));hazards[0].z=20.001f;
|
|
assert(ERR_EngineerMineRules::CorridorClear({0,0,0},{40,0,0},&hazards));
|
|
hazards[0]={20,0,0};assert(!ERR_EngineerMineRules::CorridorClear({0,0,0},{0,0,0},&hazards));
|
|
assert(ERR_EngineerMineRules::WithinWorkArea({0,0,0},{60,100,0}));assert(!ERR_EngineerMineRules::WithinWorkArea({0,0,0},{60.001f,0,0}));
|
|
for(int invalid=0;invalid<3;invalid++){Fixture f;Array<vector> prior;if(invalid==0)prior.Insert({20,0,5});if(invalid==1)prior.resize(33);if(invalid==2)prior.Insert({NAN,0,0});f.actor.position={-20,0,0};assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,{60,0,0},1000,&prior));assert(f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls);}
|
|
{Fixture f;Array<vector> prior;prior.Insert({100,0,0});assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000,&prior));prior[0]={30,0,0};f.controller.Tick(2000);assert(!f.controller.Finished()&&f.controller.m_PriorSites[0].x==100);f.actor.position={60,0,0};f.controller.Tick(3000);assert(!f.controller.Finished());auto action=f.controller.m_Action;f.actor.position={60.001f,0,0};f.controller.Tick(4000);assert(f.controller.Finished()&&action->completed==1&&!f.inventory.calls);}
|
|
{Fixture f;Array<vector> prior;prior.Insert({50,0,30});assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000,&prior));f.actor.position={50,0,11};f.controller.Tick(2000);assert(f.controller.Finished()&&!f.inventory.calls&&!f.trigger.calls);}
|
|
// Active job blocks its exact original record. Stopped pending native removal
|
|
// remains blocking even if its surviving item is destroyed; callbacks only settle.
|
|
{Fixture f;ERR_ForceRecord other;f.Begin();assert(f.controller.BlocksNextPost(&f.record)&&!f.controller.BlocksNextPost(&other)&&!f.controller.BlocksNextPost(nullptr));f.controller.Stop();assert(!f.controller.BlocksNextPost(&f.record));}
|
|
for(int completion=0;completion<3;completion++){Fixture f;f.Prepare();f.controller.Stop();auto callback=f.inventory.callback;assert(f.controller.BlocksNextPost(&f.record));f.mine.alive=false;assert(f.controller.BlocksNextPost(&f.record));if(completion==0)callback->OnComplete();if(completion==1)callback->OnFailed();if(completion==2)f.controller.m_Mine=nullptr;assert(!f.controller.BlocksNextPost(&f.record));f.Drop();callback->OnComplete();f.controller.Tick(14000);assert(f.inventory.calls==1&&!f.mine.moves&&!f.trigger.calls&&f.attempted.Count()==1);}
|
|
// An exact different physical mine can be used at a distinct post, while the
|
|
// first exact entity remains in the shared lifetime tombstone.
|
|
{Fixture f;f.Begin();f.controller.Stop();Entity second=f.mine;SCR_PressureTriggerComponent secondTrigger;InventoryItemComponent secondItem;second.trigger=&secondTrigger;second.item=&secondItem;secondItem.slot=&f.inventory;f.inventory.items={&f.mine,&second};ERR_HQTask next;ERR_HQReport nextReport;next.Destination={200,0,0};f.unit.Task=&next;f.unit.Assignment=&nextReport;f.actor.position={200,0,0};f.group.center={200,0,0};Array<vector> prior;prior.Insert({30,0,0});ERR_EngineerMine job(&f.attempted);assert(job.Begin(&f.mode,&f.unit,&next,{230,0,0},2000,&prior));assert(job.m_Mine==&second&&f.attempted.Count()==2&&f.attempted[0]==&f.mine);}
|
|
// Current native defend radius is strict and three dimensional: the broad
|
|
// 25..60 m mission band does not authorize bypassing the group's own envelope.
|
|
for(int fault=0;fault<4;fault++){Fixture f;SCR_DefendWaypoint defend;f.group.waypoint=&defend;vector point={30,0,0};
|
|
if(fault==0)f.group.utility=nullptr;
|
|
if(fault==2)point={25,17,0};
|
|
if(fault==3){f.group.waypoint=nullptr;f.groupUtility.m_fMaxAutonomousDistance=30;}
|
|
assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,point,1000));
|
|
assert(f.controller.Finished()&&f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls&&!f.trigger.calls&&!f.mode.mineIntel.candidates);
|
|
}
|
|
for(int boundary=0;boundary<3;boundary++){Fixture f;SCR_DefendWaypoint defend;f.group.waypoint=&defend;vector point={29.999f,0,0};
|
|
if(boundary==1)point={25,16,0};
|
|
if(boundary==2){f.group.waypoint=nullptr;f.group.center={1,0,0};f.groupUtility.m_fMaxAutonomousDistance=30;point={30,0,0};}
|
|
assert(f.controller.Begin(&f.mode,&f.unit,&f.task,point,1000));assert(f.attempted.Count()==1&&f.utility.additions==1);
|
|
}
|
|
// Delivered road posts now grant the 70m native defend envelope. The
|
|
// engineer still cannot leave the independently enforced 60m work area.
|
|
for(float distance:{35.f,60.f}){Fixture f;SCR_DefendWaypoint defend;defend.radius=70;f.group.waypoint=&defend;
|
|
assert(f.controller.Begin(&f.mode,&f.unit,&f.task,{distance,10,0},1000));
|
|
f.actor.position={60.01f,0,0};f.controller.Tick(2000);assert(f.controller.Finished()&&!f.trigger.calls);}
|
|
// Revalidate the current utility and its current waypoint at every phase.
|
|
// Cancellation completes only our exact action and never resumes after a late
|
|
// inventory acknowledgement or a later enlargement of the movement envelope.
|
|
for(int phase=0;phase<4;phase++)for(int replacement=0;replacement<3;replacement++){Fixture f;
|
|
if(phase==3)f.Withdraw();else if(phase==2)f.Prepare();else{f.Begin();if(phase==1){f.actor.position={30,0,0};f.controller.Tick(2000);}}
|
|
auto own=f.controller.m_Action;SCR_AIBehaviorBase foreign;f.utility.actions.Insert(&foreign);
|
|
auto callback=f.inventory.callback;int calls=f.inventory.calls,additions=f.utility.additions,moves=f.mine.moves;
|
|
SCR_DefendWaypoint defend;defend.radius=29;SCR_AIGroupUtilityComponent newUtility;newUtility.m_Owner=&f.group;newUtility.m_fMaxAutonomousDistance=29;
|
|
if(replacement==0)f.group.waypoint=&defend;else if(replacement==1)f.group.utility=&newUtility;else f.group.utility=nullptr;
|
|
f.controller.Tick(13000);
|
|
assert(f.controller.Finished()&&own->completed==1&&!foreign.completed&&f.attempted.Count()==1);
|
|
assert(f.inventory.calls==calls&&f.utility.additions==additions&&f.mine.moves==moves&&!f.trigger.calls&&!f.mode.mineIntel.seals);
|
|
f.group.utility=&f.groupUtility;f.group.waypoint=nullptr;f.Drop();if(callback)callback->OnComplete();f.actor.position={0,0,0};f.controller.Tick(14000);
|
|
assert(f.controller.Finished()&&own->completed==1&&!foreign.completed&&f.inventory.calls==calls&&f.utility.additions==additions&&f.mine.moves==moves&&!f.trigger.calls);
|
|
}
|
|
// Candidate list is captured once at accepted Begin; only successful native
|
|
// activation seals that same original list, even if carried folders change.
|
|
Fixture evidence;Entity originalFolder,lateFolder;evidence.mode.mineIntel.available.Insert(&originalFolder);evidence.Withdraw();
|
|
evidence.mode.mineIntel.available={&lateFolder};evidence.actor.position={0,0,0};evidence.controller.Tick(13000);
|
|
assert(evidence.mode.mineIntel.candidates==1&&evidence.mode.mineIntel.seals==1&&evidence.mode.mineIntel.sealed==Array<IEntity>{&originalFolder});
|
|
assert(evidence.mode.mineIntel.sealedMine==&evidence.mine&&evidence.mode.mineIntel.position.x==30);evidence.controller.Stop();evidence.controller.Tick(14000);assert(evidence.mode.mineIntel.seals==1);
|
|
Fixture noService;noService.mode.haveMineIntel=false;assert(noService.controller.Begin(&noService.mode,&noService.unit,&noService.task,{30,0,0},1000));assert(!noService.mode.mineIntel.candidates);
|
|
// Immutable total deadline, rollback/NaN clock refusal and continuous dwell.
|
|
assert(ERR_EngineerMineRules::Within(1000,1000,180999));assert(!ERR_EngineerMineRules::Within(1000,1000,181000));
|
|
assert(!ERR_EngineerMineRules::Within(1000,2000,1999));assert(!ERR_EngineerMineRules::Within(1000,2000,NAN));
|
|
assert(ERR_EngineerMineRules::PrepareSince(1000,5000,false,false)==-1);assert(ERR_EngineerMineRules::PrepareSince(1000,5000,true,true)==-1);
|
|
assert(!ERR_EngineerMineRules::Prepared(1000,10999));assert(ERR_EngineerMineRules::Prepared(1000,11000));
|
|
Fixture dwell;dwell.Begin();dwell.actor.position={30,0,0};dwell.controller.Tick(2000);dwell.controller.Tick(11999);assert(!dwell.inventory.calls);
|
|
dwell.actor.position={34,0,0};dwell.controller.Tick(12000);assert(dwell.controller.m_PreparedAt==-1);
|
|
dwell.actor.position={30,0,0};dwell.controller.Tick(12001);dwell.controller.Tick(22000);assert(!dwell.inventory.calls);dwell.controller.Tick(22001);assert(dwell.inventory.calls==1&&dwell.controller.m_Started==1000);
|
|
// Accepted unknown removal cannot retry, place or arm; original deadline wins.
|
|
Fixture unknown;unknown.Prepare();unknown.Drop();unknown.controller.Tick(12001);unknown.controller.Tick(180999);assert(unknown.inventory.calls==1&&!unknown.mine.moves&&!unknown.trigger.calls);
|
|
auto held=unknown.inventory.callback;unknown.controller.Tick(181000);assert(unknown.controller.Finished());held->OnComplete();unknown.controller.Tick(181001);assert(!unknown.mine.moves&&!unknown.trigger.calls&&unknown.attempted.Count()==1&&unknown.mode.mineIntel.seals==0);
|
|
// Synchronous completion on a false native return is evidence, not a second call.
|
|
Fixture inlineOk;inlineOk.inventory.accepted=false;inlineOk.inventory.inlineResult=1;inlineOk.Prepare();inlineOk.Drop();inlineOk.controller.Tick(12001);assert(inlineOk.controller.m_State==3&&inlineOk.inventory.calls==1);
|
|
Fixture refused;refused.inventory.accepted=false;refused.Prepare();assert(refused.controller.Finished()&&refused.inventory.calls==1);refused.inventory.callback->OnComplete();refused.Drop();refused.controller.Tick(13000);assert(!refused.trigger.calls&&!refused.mine.moves);
|
|
Fixture failed;failed.Prepare();failed.inventory.callback->OnFailed();failed.inventory.callback->OnComplete();failed.Drop();failed.controller.Tick(12001);assert(failed.controller.Finished()&&failed.inventory.callback->Result==-1&&!failed.mine.moves);
|
|
// ACK alone cannot establish custody. A stolen item is never positioned/armed.
|
|
Fixture stolen;stolen.Prepare();stolen.inventory.callback->OnComplete();Entity thief;stolen.mine.parent=&thief;stolen.Drop();stolen.controller.Tick(12001);assert(stolen.controller.Finished()&&!stolen.mine.moves);stolen.controller.Tick(181000);assert(stolen.controller.Finished());
|
|
// Observed foreign custody is terminal even if the same exact item returns nearby.
|
|
// Exercise both an acknowledged removal and an accepted, still-pending removal.
|
|
for(int ack=0;ack<2;ack++)for(int slotOnly=0;slotOnly<2;slotOnly++){Fixture f;f.Prepare();f.Drop();Entity holder;if(slotOnly)f.item.slot=&holder;else f.mine.parent=&holder;f.mine.stored=true;f.mine.storageOwner=&holder;if(ack)f.inventory.callback->OnComplete();
|
|
f.controller.Tick(12001);assert(f.controller.Finished()&&!f.mine.moves&&!f.trigger.calls);
|
|
f.mine.parent=nullptr;f.Drop();f.inventory.callback->OnComplete();f.controller.Tick(12002);f.actor.position={0,0,0};f.controller.Tick(14000);assert(!f.mine.moves&&!f.trigger.calls&&f.inventory.calls==1);}
|
|
// Native ownership still attached to the original actor may settle normally.
|
|
Fixture settling;settling.Prepare();settling.inventory.callback->OnComplete();settling.controller.Tick(12001);assert(!settling.controller.Finished()&&!settling.mine.moves);settling.Drop();settling.controller.Tick(12002);assert(settling.controller.m_State==3&&settling.mine.moves==1);
|
|
// Cancellation retains callback and item tombstone and only completes own action.
|
|
Fixture stopped;stopped.Prepare();SCR_AIBehaviorBase unrelated;stopped.utility.actions.Insert(&unrelated);auto owned=stopped.controller.m_Action;stopped.controller.Stop();stopped.controller.Stop();stopped.Drop();stopped.inventory.callback->OnComplete();stopped.controller.Tick(13000);assert(owned->completed==1&&!unrelated.completed&&!stopped.mine.moves&&!stopped.trigger.calls&&stopped.attempted.Contains(&stopped.mine)&&stopped.mode.mineIntel.seals==0);
|
|
// Original identity changes abort before irreversible work, including replacement
|
|
// task/report/ledger, recycled formation slot, capture and changed inventory.
|
|
for(int fault=0;fault<13;fault++){Fixture f;f.Begin();ERR_HQTask replacementTask;ERR_HQReport replacementReport;ERR_ForceRecord replacementRecord;SCR_InventoryStorageManagerComponent replacementInventory;SCR_AIUtilityComponent replacementUtility;
|
|
switch(fault){case 0:f.actor.alive=false;break;case 1:f.actor.player=true;break;case 2:f.unit.Task=&replacementTask;break;case 3:f.unit.Assignment=&replacementReport;break;case 4:f.ledger.record=&replacementRecord;break;case 5:f.roster.attribute=false;break;case 6:f.group.players=1;break;case 7:f.actor.inventory=&replacementInventory;break;case 8:f.agent.m_UtilityComponent=&replacementUtility;break;case 9:f.unit.PendingTask=&replacementTask;break;case 10:f.unit.ReservedForSupport=true;break;case 11:f.unit.InfantryCombat.Held=true;break;case 12:f.mode.active=false;break;}
|
|
f.controller.Tick(2000);assert(f.controller.Finished()&&!f.inventory.calls&&!f.trigger.calls);}
|
|
// Ground item lost, moved or activated externally after acknowledged removal.
|
|
for(int fault=0;fault<3;fault++){Fixture f;f.Withdraw();if(fault==0)f.mine.parent=&f.actor;if(fault==1)f.mine.position={40,0,0};if(fault==2)f.trigger.active=true;f.actor.position={0,0,0};f.controller.Tick(13000);assert(f.controller.Finished()&&!f.trigger.calls);}
|
|
Fixture arm;arm.Withdraw();arm.actor.position={10.001f,0,0};arm.controller.Tick(13000);assert(!arm.trigger.calls);arm.actor.position={10,0,0};
|
|
Entity friendly;FactionAffiliationComponent affiliation;affiliation.faction=&arm.faction;friendly.affiliation=&affiliation;game.world.query.Insert(&friendly);arm.controller.Tick(14000);assert(!arm.trigger.calls);game.world.query.clear();arm.controller.Tick(14500);assert(!arm.trigger.calls);arm.controller.Tick(15000);assert(arm.trigger.calls==1&&arm.trigger.user==&arm.actor&&arm.controller.Finished());arm.controller.Tick(16000);assert(arm.trigger.calls==1);
|
|
Fixture rejected;rejected.Withdraw();rejected.trigger.allow=false;rejected.actor.position={0,0,0};rejected.controller.Tick(13000);assert(rejected.trigger.calls==1&&!rejected.trigger.active&&rejected.controller.Finished());rejected.controller.Tick(14000);assert(rejected.trigger.calls==1&&rejected.mode.mineIntel.seals==0);
|
|
Fixture moving;moving.Withdraw();moving.actor.position={0,0,0};moving.physics.velocity={1,0,0};moving.controller.Tick(13000);assert(!moving.trigger.calls);moving.physics.velocity={0,0,0};moving.controller.Tick(14000);assert(moving.trigger.calls==1);
|
|
Fixture bounded;bounded.Withdraw();bounded.actor.position={0,0,0};Entity prop;prop.character=false;for(int i=0;i<257;i++)game.world.query.Insert(&prop);bounded.controller.Tick(13000);assert(!bounded.trigger.calls&&bounded.controller.m_FriendlyNear);
|
|
// Exact original item stays quarantined across controllers; never adopted twice.
|
|
Fixture replay;replay.Begin();replay.controller.Stop();ERR_EngineerMine second{&replay.attempted};assert(!second.Begin(&replay.mode,&replay.unit,&replay.task,{30,0,0},2000)&&replay.utility.additions==1);
|
|
Fixture cap;cap.attempted.resize(256,nullptr);assert(!cap.controller.Begin(&cap.mode,&cap.unit,&cap.task,{30,0,0},1000)&&!cap.utility.additions);
|
|
// Utility replacement or action removal must not complete somebody else's work.
|
|
Fixture swapped;swapped.Begin();auto original=swapped.controller.m_Action;SCR_AIUtilityComponent foreign;swapped.agent.m_UtilityComponent=&foreign;swapped.controller.Stop();assert(!original->completed);
|
|
Fixture removed;removed.Begin();original=removed.controller.m_Action;removed.utility.actions.clear();removed.controller.Stop();assert(!original->completed);
|
|
// Acknowledgement does not authorize correcting a stolen/far/falling item.
|
|
for(int fault=0;fault<4;fault++){Fixture f;f.Prepare();f.Drop();f.inventory.callback->OnComplete();if(fault==0)f.mine.position={40,0,0};if(fault==1)f.mine.position={30,4,0};if(fault==2)f.actor.position={35,0,0};if(fault==3)f.mine.alive=false;f.controller.Tick(12001);assert(f.controller.Finished()&&!f.mine.moves&&!f.trigger.calls);}
|
|
// Every irreversible stage repeats original task/actor custody checks.
|
|
for(int fault=0;fault<7;fault++){Fixture f;f.Withdraw();f.actor.position={0,0,0};ERR_HQTask newer;ERR_HQReport newerReport;ERR_GameMode newerMode;SCR_PressureTriggerComponent foreignTrigger;
|
|
switch(fault){case 0:f.unit.Task=&newer;break;case 1:f.unit.Assignment=&newerReport;break;case 2:f.actor.alive=false;break;case 3:f.actor.player=true;break;case 4:ERR_GameMode::current=&newerMode;break;case 5:f.mine.trigger=&foreignTrigger;break;case 6:f.task.ExpiresAt=13000;break;}
|
|
f.controller.Tick(13000);assert(f.controller.Finished()&&!f.trigger.calls&&!foreignTrigger.calls&&f.mode.mineIntel.seals==0);}
|
|
// Unknown or unconscious friendlies still block; hostile bodies and dead bodies
|
|
// do not fabricate a friendly. Static scenery isn't a soldier or vehicle.
|
|
Fixture friendPolicy;friendPolicy.Begin();Entity unknownPerson,unconscious,dead,enemy,scenery;DamageManagerComponent destroyed;destroyed.destroyed=true;dead.damage=&destroyed;FactionAffiliationComponent enemyAff;enemyAff.faction=&friendPolicy.hostile;enemy.affiliation=&enemyAff;scenery.character=false;
|
|
friendPolicy.controller.m_FriendlyNear=false;assert(!friendPolicy.controller.CheckFriendly(&unknownPerson)&&friendPolicy.controller.m_FriendlyNear);
|
|
friendPolicy.controller.m_FriendlyNear=false;unconscious.alive=false;assert(!friendPolicy.controller.CheckFriendly(&unconscious)&&friendPolicy.controller.m_FriendlyNear);
|
|
friendPolicy.controller.m_FriendlyNear=false;assert(friendPolicy.controller.CheckFriendly(&dead)&&!friendPolicy.controller.m_FriendlyNear);assert(friendPolicy.controller.CheckFriendly(&enemy));assert(friendPolicy.controller.CheckFriendly(&scenery));
|
|
// Already-armed/default-live/unknown stock is not adopted or newly quarantined.
|
|
for(int fault=0;fault<5;fault++){Fixture f;if(fault==0)f.trigger.active=true;if(fault==1)f.trigger.byDefault=true;if(fault==2)f.mine.alive=false;if(fault==3)f.prefab.name="unrelated";if(fault==4)f.mine.storageOwner=nullptr;assert(!f.controller.Begin(&f.mode,&f.unit,&f.task,{30,0,0},1000));assert(f.attempted.empty()&&!f.utility.additions&&!f.inventory.calls&&f.mode.mineIntel.candidates==0&&f.mode.mineIntel.seals==0);}
|
|
Fixture clock;clock.Begin();clock.controller.Tick(2000);clock.controller.Tick(1999);assert(clock.controller.Finished());
|
|
Fixture server;server.Begin();Replication::server=false;server.controller.Tick(2000);assert(server.controller.Finished()&&!server.inventory.calls);Replication::server=true;
|
|
|
|
}
|
|
''')
|
|
print('PASS: actual engineer native group-envelope admission and phase cancellation, preparation/deadline, original custody/identity, once-only callback removal, cancellation quarantine, withdrawal/clearance, native refusal and owned-action cleanup; native execution untested')
|