Capture current mission, AI, map and administration development
Include the current addon, terrain integrations, tools, regression tests and recorded development evidence.
This commit is contained in:
@@ -9,24 +9,25 @@ def adapt(s):
|
||||
s=s.replace('protected ','').replace('out vector position','vector& position').replace('null','nullptr')
|
||||
s=re.sub(r'array<IEntity> (\w+) = \{\};',r'Array<IEntity> \1;',s)
|
||||
s=s.replace('foreach (IEntity survivor : survivors)','for (IEntity survivor : survivors)')
|
||||
for n in ('ERR_Phase','ERR_Actors','SCR_FactionManager','Replication','ERR_GameMode','ERR_ObserverReason','RplComponent','CompartmentAccessComponent','ERR_ObserverRules'):
|
||||
for n in ('ERR_Phase','ERR_Actors','SCR_FactionManager','Replication','ERR_GameMode','ERR_ObserverReason','RplComponent','CompartmentAccessComponent','ERR_ObserverRules','ERR_ObserverTargets'):
|
||||
s=s.replace(n+'.',n+'::')
|
||||
for n in ('mode','manager','life','component','target','body'):
|
||||
s=s.replace(n+'.',n+'->')
|
||||
s=s.replace('ERR_GameMode mode','ERR_GameMode* mode').replace('PlayerManager manager','PlayerManager* manager').replace('RplComponent component','RplComponent* component')
|
||||
s=s.replace('GetGame().','GetGame()->').replace('Players().','Players()->')
|
||||
s=s.replace('IEntity target;','IEntity target=nullptr;').replace('FindComponent(RplComponent)','FindComponent()')
|
||||
s=s.replace('life->Actor.', 'life->Actor->').replace('GetGame().','GetGame()->').replace('Players().','Players()->')
|
||||
s=s.replace('ERR_PlayerLife life;', 'ERR_PlayerLife life=nullptr;').replace('IEntity target;','IEntity target=nullptr;').replace('FindComponent(RplComponent)','FindComponent()')
|
||||
return s
|
||||
roster=adapt(body(core/'ERR_PlayerRoster.c','ERR_PlayerLife CurrentActor('))
|
||||
spawn=adapt(body(core/'ERR_SpawnLogic.c','protected bool SpawnPosition('))
|
||||
observe=adapt(body(core/'ERR_Observer.c','void ERR_UpdateObserverServer('))
|
||||
bodies=adapt(body(core/'ERR_ObserverTargets.c','static void Bodies(')).replace('array<IEntity> targets','Array<IEntity>& targets').replace('array<ref ERR_PlayerLife> lives = {};','Array<ERR_PlayerLife> lives;').replace('foreach (ERR_PlayerLife life : lives)', 'for (ERR_PlayerLife life : lives)')
|
||||
run(r'''
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
using vector=int;using Faction=int;using RplId=int;
|
||||
template<class T>struct Array:std::vector<T>{int Count(){return this->size();}bool IsEmpty(){return this->empty();}void RemoveOrdered(int i){this->erase(this->begin()+i);}int Find(T a){auto p=std::find(this->begin(),this->end(),a);return p==this->end()?-1:p-this->begin();}};
|
||||
template<class T>struct Array:std::vector<T>{bool Contains(T a){return Find(a)>=0;}void Insert(T a){this->push_back(a);}int Count(){return this->size();}bool IsEmpty(){return this->empty();}void RemoveOrdered(int i){this->erase(this->begin()+i);}int Find(T a){auto p=std::find(this->begin(),this->end(),a);return p==this->end()?-1:p-this->begin();}};
|
||||
struct RplComponent{int id=1;int Id(){return id;}static RplComponent* Cast(void* p){return static_cast<RplComponent*>(p);}};
|
||||
struct Actor{bool alive=true,conscious=true;int faction=1,origin=123;Actor* vehicle=nullptr;RplComponent replication;int GetOrigin(){return origin;}void* FindComponent(){return &replication;}};using IEntity=Actor*;
|
||||
struct Identity{int id=1;bool IsNull(){return id==0;}};
|
||||
@@ -35,22 +36,25 @@ struct PlayerManager{std::map<int,IEntity> controlled;int GetPlayerIdFromControl
|
||||
struct Game{PlayerManager* manager;PlayerManager* GetPlayerManager(){return manager;}};Game* game_;Game* GetGame(){return game_;}
|
||||
namespace Replication{bool server=true;int bumps=0;bool IsServer(){return server;}void BumpMe(){bumps++;}}
|
||||
namespace ERR_Phase{const int PRISON=1;}
|
||||
namespace ERR_ObserverReason{const int NONE=0,UNCONSCIOUS=4;}
|
||||
namespace ERR_ObserverReason{const int NONE=0,SPENT_LIFE=1,UNCONSCIOUS=4;}
|
||||
namespace ERR_ObserverRules{
|
||||
'''+adapt(body(core/'ERR_Observer.c','static bool Casualty('))+r'''
|
||||
}
|
||||
namespace ERR_Actors{bool Alive(IEntity a){return a&&a->alive;}bool CanFight(IEntity a){return Alive(a)&&a->conscious;}void Players(Array<IEntity>& out,bool conscious=false){out.clear();for(auto& p:GetGame()->manager->controlled){IEntity a=p.second;if(Alive(a)&&(!conscious||a->conscious))out.push_back(a);}}}
|
||||
namespace SCR_FactionManager{int SGetFaction(IEntity a){return a?a->faction:0;}}
|
||||
namespace CompartmentAccessComponent{IEntity GetVehicleIn(IEntity a){return a?a->vehicle:nullptr;}}
|
||||
struct Roster{std::map<int,ERR_PlayerLife> lives;ERR_PlayerLife Get(int id){auto p=lives.find(id);return p==lives.end()?nullptr:p->second;}
|
||||
struct Roster{std::map<int,ERR_PlayerLife> lives;void Participants(Array<ERR_PlayerLife>& out){for(auto p:lives)if(p.second->UsedLife)out.push_back(p.second);}ERR_PlayerLife Get(int id){auto p=lives.find(id);return p==lives.end()?nullptr:p->second;}
|
||||
'''+roster+r'''
|
||||
};
|
||||
struct ERR_GameMode{static ERR_GameMode* current;Roster* roster;int phase=2,faction=1;bool active=true;static ERR_GameMode* Get(){return current;}bool Active(){return active;}int Phase(){return phase;}int PlayerFaction(){return faction;}int PrisonPosition(){return 0;}float PrisonHeading(){return 90;}Roster* Players(){return roster;}};ERR_GameMode* ERR_GameMode::current=nullptr;
|
||||
namespace ERR_ObserverTargets {
|
||||
'''+bodies+r'''
|
||||
}
|
||||
struct Placement{int nearCalls=0,prisonCalls=0;IEntity last=nullptr;bool clear=true;bool Near(IEntity a,int,vector& p){nearCalls++;last=a;p=a->origin;return clear;}bool Prison(int,int,vector& p,float heading){assert(heading==90);prisonCalls++;p=0;return true;}};
|
||||
struct Spawn{Placement m_Placement;int m_AuthorizedPositions=0;
|
||||
'''+spawn+r'''
|
||||
};
|
||||
struct Observer{int m_ERRObserverReason=1,m_ERRObserverAnchor=-1,m_ERRObservedId=-1,releases=0,streamRequests=0;IEntity m_ERRServerObserved=nullptr,m_ERRServerObservedVehicle=nullptr,own=nullptr;IEntity GetControlledEntity(){return own;}
|
||||
struct Observer{int playerId=0;int GetPlayerId(){return playerId;}int m_ERRObserverReason=1,m_ERRObserverAnchor=-1,m_ERRObservedId=-1,releases=0,streamRequests=0;IEntity m_ERRServerObserved=nullptr,m_ERRServerObservedVehicle=nullptr,own=nullptr;IEntity GetControlledEntity(){return own;}
|
||||
void ERR_ReleaseObserved(){releases++;m_ERRServerObserved=nullptr;m_ERRServerObservedVehicle=nullptr;m_ERRObservedId=-1;}
|
||||
void ERR_StreamObserved(IEntity,bool){streamRequests++;}
|
||||
'''+observe+r'''
|
||||
@@ -69,7 +73,7 @@ int main(){
|
||||
life.ConnectionId=2;rejected();life.ConnectionId=1;
|
||||
manager.controlled[1]=&replacement;rejected(); // recycled connection ID/controlled replacement cannot inherit original body record
|
||||
manager.controlled[1]=&original;accepted(); // restored canonical handoff
|
||||
original.alive=false;rejected();original.alive=true;
|
||||
original.alive=false;Observer corpse;corpse.ERR_UpdateObserverServer();assert(corpse.m_ERRServerObserved==&original);Spawn noCorpseSpawn;vector deadPos;noCorpseSpawn.SpawnPosition(&mode,deadPos);assert(noCorpseSpawn.m_Placement.nearCalls==0);original.alive=true;
|
||||
original.faction=2;rejected();original.faction=1;
|
||||
mode.faction=0;rejected();mode.faction=1;
|
||||
mode.roster=nullptr;rejected();mode.roster=&roster;
|
||||
@@ -90,10 +94,39 @@ int main(){
|
||||
life.Connected=true;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==4);
|
||||
mode.active=false;casualty.ERR_UpdateObserverServer();assert(casualty.m_ERRObserverReason==0&&!casualty.m_ERRServerObserved);
|
||||
mode.active=true;original.conscious=true;
|
||||
// A dead/disconnected teammate remains selectable while another is alive.
|
||||
original.alive=false;life.Connected=false;manager.controlled.erase(1);
|
||||
Observer mixedDead;mixedDead.ERR_UpdateObserverServer();assert(mixedDead.m_ERRServerObserved==&other);
|
||||
mixedDead.ERR_UpdateObserverServer(1);assert(mixedDead.m_ERRServerObserved==&original);
|
||||
mixedDead.ERR_UpdateObserverServer(0);assert(mixedDead.m_ERRServerObserved==&original); // polling retains selected corpse
|
||||
mixedDead.ERR_UpdateObserverServer(-1);assert(mixedDead.m_ERRServerObserved==&other);
|
||||
// Every participant dead, with native controllers already detached: cycle both bodies, including own.
|
||||
original.alive=false;other.alive=false;manager.controlled.clear();life.Connected=false;second.Connected=false;
|
||||
Observer allDead;allDead.own=&original;allDead.m_ERRObserverReason=4;
|
||||
allDead.ERR_UpdateObserverServer();assert(allDead.m_ERRObserverReason==1&&allDead.m_ERRServerObserved==&original);
|
||||
allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&other);
|
||||
allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&original);
|
||||
allDead.ERR_UpdateObserverServer(-1);assert(allDead.m_ERRServerObserved==&other);
|
||||
second.Actor=nullptr;allDead.ERR_UpdateObserverServer();assert(allDead.m_ERRServerObserved==&original);second.Actor=&other;
|
||||
other.faction=2;allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&original);
|
||||
other.faction=0;allDead.ERR_UpdateObserverServer(1);assert(allDead.m_ERRServerObserved==&other);other.faction=1; // detached corpse without affiliation is still a canonical player body
|
||||
original.alive=true;other.alive=true;life.Connected=true;second.Connected=true;manager.controlled[1]=&original;manager.controlled[2]=&other;
|
||||
allDead.ERR_UpdateObserverServer();assert(allDead.m_ERRObserverReason==0); // healed body takes camera back
|
||||
// Solo death without entity-lost callback still selects the controlled corpse.
|
||||
roster.lives.erase(2);manager.controlled.erase(2);original.alive=false;
|
||||
Observer solo;solo.playerId=1;solo.own=&original;solo.m_ERRObserverReason=0;
|
||||
solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==1&&solo.m_ERRServerObserved==&original&&solo.m_ERRObserverAnchor==original.origin);
|
||||
// Native detachment before the callback is equally covered by the roster.
|
||||
solo.own=nullptr;solo.m_ERRObserverReason=0;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==1&&solo.m_ERRServerObserved==&original);
|
||||
mode.active=false;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==0);mode.active=true;
|
||||
life.UsedLife=false;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==0);life.UsedLife=true;
|
||||
// A new living controlled body must not inherit its predecessor's death camera.
|
||||
solo.own=&replacement;solo.ERR_UpdateObserverServer();assert(solo.m_ERRObserverReason==0);
|
||||
original.alive=true;roster.lives[2]=&second;manager.controlled[2]=&other;
|
||||
// Existing terrain rejection and prison-phase placement stay delegated.
|
||||
Spawn blocked;blocked.m_Placement.clear=false;assert(blocked.SpawnPosition(&mode,p)&&p==0&&blocked.m_Placement.nearCalls==2);
|
||||
mode.phase=ERR_Phase::PRISON;Spawn prison;assert(prison.SpawnPosition(&mode,p)&&p==0&&prison.m_Placement.nearCalls==0);
|
||||
Replication::server=false;Observer client;client.ERR_UpdateObserverServer();assert(!client.m_ERRServerObserved&&client.streamRequests==0);Replication::server=true;
|
||||
}
|
||||
''')
|
||||
print('PASS: actual CurrentActor, SpawnPosition and observer-selection methods reject pending/unregistered/replaced/recycled/foreign/dead bodies; canonical selection/cycling, unconscious following and prison/geometry fallback preserved; native boundaries adapted')
|
||||
print('PASS: actual CurrentActor, SpawnPosition and observer-selection methods reject pending/unregistered/replaced/recycled/foreign bodies; canonical live selection and roster-corpse cycling, unconscious following and prison/geometry fallback preserved; native boundaries adapted')
|
||||
|
||||
Reference in New Issue
Block a user