"""Execute leisure scene progression, native flight handover and pet-walk gating.""" from pathlib import Path import subprocess,tempfile root=Path(__file__).resolve().parents[2] source=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text() def function(signature): a=source.index(signature);b=source.index('{',a);depth=1;e=b+1 while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1 return source[a:e] code=r''' #include #include "AdventureInstancePolicy.h" #include "AdventureGroupPolicy.h" #include #include #include #include #include #include #include #include namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}} using uint32=uint32_t; #define TC_LOG_INFO(...) ((void)0) constexpr float INVALID_HEIGHT=-100000; constexpr int BASE_ATTACK=0,ITEM_SUBCLASS_WEAPON_FISHING_POLE=20,UNIT_NPC_EMOTESTATE=0,EMOTE_STATE_FISHING=379; struct Player; struct Position{ float m_positionX=0,m_positionY=0,m_positionZ=0,o=0; void Relocate(float x,float y,float z){m_positionX=x;m_positionY=y;m_positionZ=z;} void Relocate(Player*);float GetPositionX()const{return m_positionX;}float GetPositionY()const{return m_positionY;}float GetPositionZ()const{return m_positionZ;} float GetExactDist(Position const&p)const{return std::hypot(std::hypot(m_positionX-p.m_positionX,m_positionY-p.m_positionY),m_positionZ-p.m_positionZ);} }; struct PositionFullTerrainStatus{bool outdoors=true;}; struct Terrain{ bool outdoors=true;float outdoorAfter=1e9;uint32 zone=1519; void GetFullTerrainStatusForPosition(int,uint32,float x,float y,float,PositionFullTerrainStatus& s){s.outdoors=outdoors || std::hypot(x,y)>outdoorAfter;} void GetZoneAndAreaId(int,uint32,uint32& z,uint32& a,Position const&){z=zone;a=1;} }terrain; struct Map{float water=-1000,ground=0;Terrain* GetTerrain(){return &terrain;}float GetHeight(int,float,float,float,bool,float){return ground;}float GetWaterLevel(int,float,float){return water;}}map; struct Guid{uint32 GetCounter(){return 26;}}; struct Item{struct Template{uint32 GetSubClass(){return 20;}}t;Template* GetTemplate(){return &t;}}rod; struct Creature:Position{}cat; struct Player:Position{ bool alive=true,combat=false,flying=false,outdoors=true,water=false,falling=false,hasRod=false;uint32 emote=0;int stops=0; uint32 GetMapId(){return 0;}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsFlying(){return flying;}bool IsInFlight(){return false;}bool GetTransport(){return false;} bool IsOutdoors(){return outdoors;}bool IsInWater(){return water;}bool IsFalling(){return falling;}Guid GetGUID(){return {};} int GetPhaseShift(){return 0;}Map* GetMap(){return ↦}Item* GetWeaponForAttack(int,bool){return hasRod?&rod:nullptr;} void SetFacingTo(float f){o=f;}float GetOrientation(){return o;}void SetUInt32Value(int,uint32 v){emote=v;}uint32 GetUInt32Value(int){return emote;} bool IsWithinLOS(float,float,float){return true;} void StopMoving(){++stops;}float GetExactDist(Position const& p){return Position::GetExactDist(p);}float GetExactDist(Creature* p){return Position::GetExactDist(*p);} }; void Position::Relocate(Player*p){*this=*p;} bool Unsafe(Player* p){return p->combat||p->flying;} constexpr int PATHFIND_NORMAL=1; struct PathGenerator{ struct Point{float x,y,z;};std::vector path; static inline bool reachable=true,broken=false;static inline int type=1;static inline float bend=20; PathGenerator(Player*){}void SetPathLengthLimit(float){} bool CalculatePath(Position const&a,Position const&b){ path.clear();int n=int(a.GetExactDist(b)/4)+1; for(int i=0;i<=n;++i){float t=float(i)/n;path.push_back({a.m_positionX+(b.m_positionX-a.m_positionX)*t,a.m_positionY+(b.m_positionY-a.m_positionY)*t+bend*std::sin(3.14159265f*t),a.m_positionZ+(b.m_positionZ-a.m_positionZ)*t});} if(broken && path.size()>5) path[5].z+=10; return reachable; } int GetPathType(){return type;}auto const& GetPath(){return path;} }; struct ClientlessCompanionScene{uint32 LeashSpell=0;bool Active=false,Ready=false,Leashed=false;}; struct ClientlessCompanionMgr{ static inline bool available=true,ready=true,valid=true;static inline int starts=0,resets=0; static bool Available(Player*,uint32){return available;} static bool Start(Player*,ClientlessCompanionScene& s,uint32,uint32){s.Active=true;s.Ready=false;++starts;return available;} static bool Update(Player*,ClientlessCompanionScene& s,uint32){s.Ready=ready;return valid;} static Creature* Resolve(Player*,ClientlessCompanionScene const&){return valid?&cat:nullptr;} static void Reset(Player*,ClientlessCompanionScene& s){if(s.Active)++resets;s={};} }; struct Profile{bool CanGroundMount=true;float GroundMountMinDistance=45;std::vector GroundMounts{1},Companions;uint32 CompanionSpell=10673,CompanionLeashItem=37460;}profile; struct Lua{uint32 SelectCompanionSpell(uint32){return profile.CompanionSpell;}int mounts=0,dismounts=0;Profile* GetAdventureProfile(uint32){return &profile;}bool PrepareGroundMount(Player*,float,char const*){++mounts;return true;}void DismountGroundMount(Player*,char const*){++dismounts;}}lua;auto sClientlessLuaEngine=&lua; struct CombatMgr{bool available=true;int starts=0;bool TrainingAvailable(Player*){return available;}bool Train(Player*,uint32 duration,time_t){assert(duration>0);++starts;return available;}}combatMgr;auto sAdventureCombatMgr=&combatMgr; struct AdventureMicroAction{std::string Requirement;}; struct Location{struct{std::string Category="leisure";float O=0;}Point;bool IsArea=false;uint32 AreaId=0,ZoneId=1519;}location; struct Locations{Location* Find(std::string const&){return &location;}}locations;auto sAdventureLocationMgr=&locations; enum class ClientlessTravelState{Moving,Arrived,PathRejected}; struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float FinalDistance=0,PathLength=1;}; struct Travel{bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics&){return true;}struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;int cancels=0;void CancelTravel(uint32){++cancels;}auto GetStatus(uint32){return &status;}}travel;auto sClientlessTravelMgr=&travel; enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed}; struct Flight{struct{ClientlessFlightState State=ClientlessFlightState::Mounting;}status;int starts=0;bool enabled=true;Position target;bool Enabled(Player*){return enabled;}bool Start(Player*,Position const&t,std::string&){++starts;target=t;status.State=ClientlessFlightState::Mounting;return true;}auto GetStatus(uint32){return &status;}}flight;auto sClientlessFlightMgr=&flight; struct Manager{void ClearQueuedActions(uint32){}}manager;auto sClientlessPlayerMgr=&manager; struct Groups{unsigned cancellations=0;void CancelBrowser(Player*){++cancellations;}}groups;auto sAdventureGroupMgr=&groups; struct Executor{bool RequestStopAtNode(uint32){return false;}}executor;auto sClientlessActionExecutorMgr=&executor; class AdventureBehaviorMgr{public: enum class Phase{Idle,OnSite,Switching}; struct Record{ AdventureInstancePolicy::Visit Instance; std::string Intent="leisure",Location="city",WantedIntent,WantedLocation,Detail;Position Anchor;uint32 AnchorMap=0,MicroTimer=240000,RetryTimer=0;std::mt19937 Random{26}; time_t Deadline=0,ExpeditionUntil=0;bool Dirty=false,ExpeditionStarted=false;std::string ExpeditionIntent;std::unordered_set Tried;Phase State=Phase::OnSite; struct LeisureScene{bool Active=false,Flying=false,Moving=false,StopRequested=false,Returning=false,PlannedWalk=false,Progressive=false;std::string Kind;Position Origin;std::vector Route,WalkTrace;std::size_t Step=0,OutboundSteps=0;uint32 Elapsed=0,LegElapsed=0,PauseMs=0,CompanionWait=0,CompletedLegs=0;ClientlessCompanionScene Companion;}Leisure; }; int moves=0,resets=0;bool lastRiding=false,moveAllowed=true;Position requested; bool LocalMove(Player*,Record&,Position const& target,float,bool riding=false){++moves;lastRiding=riding;requested=target;travel.status.State=ClientlessTravelState::Moving;return moveAllowed;} void ResetMicro(Player* p,Record&r){++resets;ResetLeisure(p,r);} void FinishInstance(Player*,Record&r){r.Instance={};} bool workAvailable=false;bool ProfessionWorkAvailable(Player*,Record&){return workAvailable;} bool FishingDirection(Player*,float&)const; bool LeisureAvailable(Player*,Record const&,AdventureMicroAction const&)const; bool PlanLeisureWalk(Player*,Record&); bool StartLeisure(Player*,Record&,AdventureMicroAction const&); bool UpdateLeisure(Player*,Record&,uint32);void ResetLeisure(Player*,Record&); void SetWanted(Player*,Record&,std::string const&,std::string const&,time_t); }; ''' for sig in ('bool AdventureBehaviorMgr::FishingDirection(', 'bool AdventureBehaviorMgr::PlanLeisureWalk(', 'bool AdventureBehaviorMgr::LeisureAvailable(', 'bool AdventureBehaviorMgr::StartLeisure(', 'bool AdventureBehaviorMgr::UpdateLeisure(', 'void AdventureBehaviorMgr::ResetLeisure(', 'void AdventureBehaviorMgr::SetWanted('):code+=function(sig)+'\n' code+=r''' int main(){ AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p; assert(mgr.StartLeisure(&p,r,{"walk"})&&r.Leisure.PlannedWalk); auto expected=r.Leisure.Route.size();auto outward=r.Leisure.OutboundSteps; assert(outward>=3&&expected==2*outward); assert(r.Leisure.Origin.GetExactDist(r.Leisure.Route[outward-1])>=65); for(size_t i=0;i+10); bool following=true;for(int i=0;i<25&&following;++i)following=mgr.UpdateLeisure(&p,r,1000); assert(!following);mgr.ResetLeisure(&p,r);assert(ClientlessCompanionMgr::resets==1); cat.Relocate(0,0,0);p.Relocate(0,0,0); // Leashed walking stops before native 5 yd range and waits for a real catch-up. assert(mgr.StartLeisure(&p,r,{"pet_walk"}));r.Leisure.Companion.Leashed=true; before=mgr.moves;cat.Relocate(4.1f,0,0); assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before&&r.Leisure.CompanionWait==50); cat.Relocate(3.7f,0,0);assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before); cat.Relocate(3,0,0);assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before+1&&r.Leisure.CompanionWait==0); mgr.ResetLeisure(&p,r); // Mounted outings use the same navigation with an explicitly selected riding gait. assert(mgr.StartLeisure(&p,r,{"ride"}));assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.lastRiding&&lua.mounts>0);mgr.ResetLeisure(&p,r); // A flight switch waits for native landing and a return to its origin. assert(mgr.StartLeisure(&p,r,{"flight"}));assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Flying); p.flying=true;int resets=mgr.resets;mgr.SetWanted(&p,r,"rest","inn",1234); assert(r.Dirty&&r.Leisure.StopRequested&&mgr.resets==resets&&r.WantedIntent=="rest"); assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0); // mounting/flying cannot fabricate arrival p.flying=false;flight.status.State=ClientlessFlightState::Arrived; assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving); p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),0);travel.status.State=ClientlessTravelState::Arrived; assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Returning&&r.Leisure.Route.size()==1&&r.Leisure.Flying); flight.status.State=ClientlessFlightState::Arrived;assert(mgr.UpdateLeisure(&p,r,1000)); p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),0);travel.status.State=ClientlessTravelState::Arrived; assert(!mgr.UpdateLeisure(&p,r,1000)&&p.GetExactDist(r.Leisure.Origin)<0.01f);mgr.ResetLeisure(&p,r); // Angling needs an authored bank, equipped fishing rod and real water. assert(!mgr.LeisureAvailable(&p,r,{"fish"}));location.Point.Category="leisure_fishing";p.hasRod=true; assert(!mgr.StartLeisure(&p,r,{"fish"}));map.water=2; assert(mgr.StartLeisure(&p,r,{"fish"})&&p.emote==EMOTE_STATE_FISHING); r.MicroTimer=0;assert(!mgr.UpdateLeisure(&p,r,1000));mgr.ResetLeisure(&p,r); // Adventure foot exploration reuses coherent native routes, never pet/flight scenes. r.Intent="adventure";r.MicroTimer=180000;p.outdoors=true;p.Relocate(0,0,0); assert(mgr.LeisureAvailable(&p,r,{"explore"})&&mgr.LeisureAvailable(&p,r,{"patrol"})); assert(!mgr.LeisureAvailable(&p,r,{"pet_walk"})&&!mgr.LeisureAvailable(&p,r,{"flight"})); assert(mgr.StartLeisure(&p,r,{"explore"})&&r.Leisure.PlannedWalk&&r.Leisure.Route.size()>=6); mgr.ResetLeisure(&p,r);p.outdoors=false;assert(!mgr.LeisureAvailable(&p,r,{"explore"})); // Profession area search progresses outward and stops only at confirmed arrival. p.outdoors=true;p.Relocate(0,0,0);r.Intent="profession";r.MicroTimer=180000; location.IsArea=true;location.AreaId=1519; assert(mgr.StartLeisure(&p,r,{"work_search"})&&r.Leisure.Progressive); assert(r.Leisure.Route.size()==r.Leisure.OutboundSteps); assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving); mgr.workAvailable=true; assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0); p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),mgr.requested.GetPositionZ()); travel.status.State=ClientlessTravelState::Arrived; assert(!mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==1&&r.Leisure.Route.size()==1); mgr.ResetLeisure(&p,r);mgr.workAvailable=false; // Profession fishing needs real shore water and a rod, without a fishing location key. location.Point.Category="zone";map.water=-1000;p.hasRod=true; assert(!mgr.LeisureAvailable(&p,r,{"fish"}));map.water=2; assert(mgr.LeisureAvailable(&p,r,{"fish"}));p.hasRod=false; assert(!mgr.LeisureAvailable(&p,r,{"fish"}));p.hasRod=true;p.water=true; assert(!mgr.LeisureAvailable(&p,r,{"fish"}));p.water=false; r.Intent="leisure";combatMgr.available=false;assert(!mgr.LeisureAvailable(&p,r,{"training"})); combatMgr.available=true;assert(mgr.LeisureAvailable(&p,r,{"training"})); assert(mgr.StartLeisure(&p,r,{"training"}) && combatMgr.starts==1); r.Intent="adventure";assert(!mgr.LeisureAvailable(&p,r,{"training"})); // Recruitment walks leave the inn, stay in the capital and retrace the route. mgr.ResetLeisure(&p,r);r.Intent="groupformation";r.MicroTimer=180000;terrain.outdoors=false;terrain.outdoorAfter=40;p.outdoors=false;PathGenerator::bend=2; assert(mgr.StartLeisure(&p,r,{"walk"})); assert(r.Leisure.Origin.GetExactDist(r.Leisure.Route[r.Leisure.OutboundSteps-1])>=65); for(auto const& point:r.Leisure.Route)assert(point.GetExactDist(r.Leisure.Origin)<=250); mgr.ResetLeisure(&p,r);terrain.outdoorAfter=1e9;assert(!mgr.StartLeisure(&p,r,{"walk"})); terrain.outdoorAfter=40;assert(mgr.StartLeisure(&p,r,{"walk"})); r.Deadline=GameTime::now+300;assert(!mgr.LeisureAvailable(&p,r,{"walk"}));r.Deadline=GameTime::now+301;assert(mgr.LeisureAvailable(&p,r,{"walk"}));r.Deadline=0; assert(!mgr.LeisureAvailable(&p,r,{"ride"}));mgr.ResetLeisure(&p,r);terrain.outdoors=true;PathGenerator::bend=20; r.RetryTimer=30000;r.Tried.insert("old");mgr.SetWanted(&p,r,"rest","new",1234);assert(r.RetryTimer==0&&r.Tried.empty()&&r.Deadline==1234); // Group travel latches survive the formation-to-activity transition but expire. r.WantedIntent="groupformation";r.ExpeditionIntent="instance";r.ExpeditionUntil=GameTime::now+600; mgr.SetWanted(&p,r,"instance","entrance",GameTime::now+600);assert(r.ExpeditionStarted); r.WantedIntent="groupformation";r.ExpeditionUntil=GameTime::now; mgr.SetWanted(&p,r,"instance","entrance",GameTime::now+600);assert(!r.ExpeditionStarted); } ''' with tempfile.TemporaryDirectory(prefix='vcore-leisure-') as folder: cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code) subprocess.run(['c++','-std=c++17','-I'+str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True) subprocess.run([str(binary)],check=True) print('Leisure: substantial curved MMAP routes, mirrored return, interrupted return, rejected corridors, actual arrival, companion follow, riding, flight and fishing passed.')