"""Exercise production recovery selection/clock with native navigation adapters.""" from pathlib import Path import subprocess import tempfile root = Path(__file__).resolve().parents[2] source = (root / 'src/server/game/World/AdventureBehaviorMgr.cpp').read_text() def function(signature): start = source.index(signature) body = source.index('{', start) end, depth = body + 1, 1 while depth: depth += (source[end] == '{') - (source[end] == '}') end += 1 return source[start:end] code = r''' #include #include #include #include #include #include using uint32=uint32_t; #define TC_LOG_INFO(...) ((void)0) constexpr int UNIT_STAND_STATE_SLEEP=3,UNIT_STAND_STATE_SIT=1; struct Position { float x=0,y=0,z=0,o=0; Position()=default; Position(float a,float b,float c,float d=0):x(a),y(b),z(c),o(d){} float GetOrientation()const{return o;} float GetExactDist(Position const& p)const{return GetExactDist(p.x,p.y,p.z);} float GetPositionZ()const{return z;} float GetExactDist(float a,float b,float c)const{return std::sqrt((x-a)*(x-a)+(y-b)*(y-b)+(z-c)*(z-c));} }; struct Guid{uint32 GetCounter(){return 26;}}; struct Player:Position { uint32 map=0;bool water=false,falling=false,phase=true;int stand=0,poses=0; bool space=true; std::vector occupants; uint32 GetMapId(){return map;}Guid GetGUID(){return {};} bool IsInWater(){return water;}bool IsFalling(){return falling;} void GetPlayerListInGrid(std::vector& out,float){out=occupants;} bool IsInPhase(Player* p){return p->phase;} void SetFacingTo(float){}void SetStandState(int value){stand=value;++poses;} int GetStandState(){return stand;} }; bool SleepSpace(Player* p,Position const&){return p->space;} std::vector candidates; std::vector SleepCandidates(Player*,Position const&,bool){return candidates;} struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float FinalDistance=0;}; double Novelty(std::vector const&,std::string const&){return 1;} void Remember(std::vector& h,std::string const& s){h.push_back(s);} std::size_t Choose(std::vector const& weights,int&){for(std::size_t i=0;i0)return i;return weights.size();} struct AdventureMicroAction{std::string Requirement;}; struct AdventureLocation { struct {std::string Id="bed",Category="rest_sleep";uint32 MapId=0;float X=0,Y=0,Z=0,O=0;}Point; bool allowed=true; }; struct Locations { std::vector points; auto const& Locations()const{return points;} AdventureLocation const* Find(std::string const& id){for(auto const& p:points)if(p.Point.Id==id)return &p;return nullptr;} }locations; auto sAdventureLocationMgr=&locations; enum class ClientlessTravelState{Moving,Arrived,PathRejected}; struct Travel { struct Status{ClientlessTravelState State=ClientlessTravelState::Moving;}status; float endpointError=0;bool suspicious=false; bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics& d){d.FinalDistance=endpointError;d.Suspicious=suspicious;return true;} bool present=true;Status const* GetStatus(uint32){return present?&status:nullptr;} }travel; auto sClientlessTravelMgr=&travel; class AdventureBehaviorMgr { public: struct RecoveryScene{bool Active=true,Approaching=true;std::string Point="bed",Posture="sleep";uint32 Remaining=3000,ApproachElapsed=0;Position Destination;bool HasDestination=false;uint32 SpaceCheckMs=0;}; struct Record{Position Anchor;uint32 AnchorMap=0;RecoveryScene Recovery;std::string Detail;std::vector TargetHistory;int Random=0;}; bool Allowed(Player*,AdventureLocation const& p,std::string const&)const{return p.allowed;} std::vector moves;bool moveOK=true; bool LocalMove(Player*,Record&,Position const& p,int){moves.push_back(p);return moveOK;} bool StartRecovery(Player*,Record&,AdventureMicroAction const&,uint32); bool RecoveryAvailable(Player*,Record const&,AdventureMicroAction const&)const; bool UpdateRecovery(Player*,Record&,uint32); }; ''' # Avoid a class/member name collision in the adapter. code = code.replace('struct Locations {', 'struct LocationStore {') code += function('bool AdventureBehaviorMgr::RecoveryAvailable(') code += function('bool AdventureBehaviorMgr::StartRecovery(') code += function('bool AdventureBehaviorMgr::UpdateRecovery(') code += r''' int main(){ AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p; AdventureMicroAction sleep{"sleep"},ground{"ground"},unknown{"anything"}; assert(!mgr.RecoveryAvailable(&p,r,sleep)); locations.points.push_back({}); assert(mgr.RecoveryAvailable(&p,r,sleep)); assert(!mgr.RecoveryAvailable(&p,r,ground)&&!mgr.RecoveryAvailable(&p,r,unknown)); // Reject endpoints beneath an elevated surface before starting movement. candidates={Position(3,0,0)};travel.endpointError=0.7f; assert(!mgr.StartRecovery(&p,r,sleep,7000)&&mgr.moves.empty()); travel.endpointError=0;assert(mgr.StartRecovery(&p,r,sleep,7000)); assert(r.Recovery.HasDestination && r.Recovery.Destination.x==3 && mgr.moves.size()==1); assert(r.Recovery.Remaining==7000); mgr.moves.clear();r=AdventureBehaviorMgr::Record{}; // Even if a tavern is first in the catalog, explicit sleep anchors have priority. locations.points.insert(locations.points.begin(),AdventureLocation{});locations.points[0].Point.Id="inn"; locations.points[0].Point.Category="tavern"; assert(mgr.StartRecovery(&p,r,sleep,7000)&&r.Recovery.Point=="bed"); locations.points.erase(locations.points.begin());r=AdventureBehaviorMgr::Record{}; auto& point=locations.points[0]; point.Point.Category="tavern";assert(mgr.RecoveryAvailable(&p,r,sleep)); // bounded indoor floor search point.Point.Category="rest_sleep";point.Point.MapId=1;assert(!mgr.RecoveryAvailable(&p,r,sleep)); point.Point.MapId=0;point.allowed=false;assert(!mgr.RecoveryAvailable(&p,r,sleep)); point.allowed=true;point.Point.X=31;assert(!mgr.RecoveryAvailable(&p,r,sleep)); point.Point.X=0;point.Point.Z=13;assert(!mgr.RecoveryAvailable(&p,r,sleep));point.Point.Z=0; // Walking cannot consume sleep time or trigger the sleep pose. for(int n=0;n<10;++n)assert(mgr.UpdateRecovery(&p,r,1000)); assert(p.poses==0&&r.Recovery.Remaining==3000); travel.status.State=ClientlessTravelState::Arrived;p.x=2; assert(!mgr.UpdateRecovery(&p,r,1000)&&p.poses==0);p.x=0; Player occupant;p.occupants={&occupant}; assert(!mgr.UpdateRecovery(&p,r,1000)&&p.poses==0);p.occupants.clear(); assert(mgr.UpdateRecovery(&p,r,1000)&&p.stand==UNIT_STAND_STATE_SLEEP&&!r.Recovery.Approaching); assert(mgr.UpdateRecovery(&p,r,1000));assert(mgr.UpdateRecovery(&p,r,1000)); assert(!mgr.UpdateRecovery(&p,r,1000)); // full duration starts after arrival r.Recovery={};p.space=false;assert(!mgr.UpdateRecovery(&p,r,1000)&&r.Recovery.Approaching);p.space=true; r.Recovery.Destination=Position(4,0,0);r.Recovery.HasDestination=true; assert(!mgr.UpdateRecovery(&p,r,1000));p.x=4; assert(mgr.UpdateRecovery(&p,r,1000)); // chosen offset, not the old obstructed marker p.space=false;r.Recovery.SpaceCheckMs=0;assert(!mgr.UpdateRecovery(&p,r,1000));p.space=true;p.x=0; r.Recovery={};travel.status.State=ClientlessTravelState::PathRejected; assert(!mgr.UpdateRecovery(&p,r,1000)); travel.status.State=ClientlessTravelState::Moving;r.Recovery.ApproachElapsed=60000; assert(!mgr.UpdateRecovery(&p,r,1000)); r.Recovery={};travel.status.State=ClientlessTravelState::Arrived; locations.points.clear();assert(!mgr.UpdateRecovery(&p,r,1000)); // removed on catalog reload locations.points.push_back({});p.water=true;assert(!mgr.UpdateRecovery(&p,r,1000));p.water=false; p.falling=true;assert(!mgr.UpdateRecovery(&p,r,1000));p.falling=false; p.map=1;assert(!mgr.UpdateRecovery(&p,r,1000));p.map=0; r.Recovery.Posture="ground";assert(!mgr.UpdateRecovery(&p,r,1000)); locations.points[0].Point.Category="rest_ground"; assert(mgr.UpdateRecovery(&p,r,1000)&&p.stand==UNIT_STAND_STATE_SIT); p.stand=0;assert(!mgr.UpdateRecovery(&p,r,1000)); // externally interrupted posture } ''' with tempfile.TemporaryDirectory(prefix='vcore-rest-') as folder: cpp, binary = Path(folder) / 'test.cpp', Path(folder) / 'test' cpp.write_text(code) subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True) subprocess.run([str(binary)], check=True) print('Recovery: authored places, bounds, arrival, occupancy, full rest duration, failure, reload and interruption passed.')