"""Run the real hearth recovery state machine with a deterministic clock.""" from pathlib import Path import subprocess,tempfile root=Path(__file__).resolve().parents[2] s=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text() a=s.index('bool AdventureBehaviorMgr::UpdateNavigationRescue(');b=s.index('\nvoid AdventureBehaviorMgr::Update(',a) code=r''' #include "AdventureRecoveryPolicy.h" #include #include #include #include #include using uint32=unsigned; #define TC_LOG_INFO(...) ((void)0) constexpr int UNIT_NPC_EMOTESTATE=0,UNIT_STAND_STATE_STAND=0,UNIT_STATE_CASTING=1,EMOTE_STATE_SPELL_PRECAST=193,TEAM_ALLIANCE=0; struct Position{float x=0,y=0,z=0,o=0;void Relocate(float a,float b,float c,float d=0){x=a;y=b;z=c;o=d;} void Relocate(Position const*p){*this=*p;}float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;} float GetExactDist(Position const&p)const{return std::sqrt((x-p.x)*(x-p.x)+(y-p.y)*(y-p.y)+(z-p.z)*(z-p.z));}}; struct Map{bool dungeon=false;bool IsDungeon(){return dungeon;}}map; struct Session{void CompleteClientlessTeleport(){}}session; struct Motion{int expired=0;void MovementExpired(){++expired;}}motion; struct Player:Position{bool alive=true,combat=false,taxi=false,flying=false,aboard=false,falling=false,casting=false,alliance=false; unsigned mapId=1,emote=0,visual=0,teleports=0,saves=0; struct Guid{unsigned GetCounter(){return 26;}};Guid GetGUID(){return {};} bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return false;}Map*GetMap(){return ↦} unsigned GetMapId(){return mapId;}unsigned GetTeamId(){return alliance?0:1;}bool IsInFlight(){return taxi;}bool IsFlying(){return flying;}bool GetTransport(){return aboard;} bool IsFalling(){return falling;}bool HasUnitState(int){return casting;}void StopMoving(){}void SetStandState(int){} void SetUInt32Value(int,unsigned n){emote=n;}void SendPlaySpellVisualKit(unsigned kit,unsigned,unsigned duration){assert(kit==36&&duration==10000);++visual;} Motion*GetMotionMaster(){return &motion;}void CleanupAfterTaxiFlight(){taxi=false;} bool TeleportTo(unsigned m,float a,float b,float c,float d){mapId=m;Relocate(a,b,c,d);++teleports;return true;} Session*GetSession(){return &session;}void SaveToDB(){++saves;} }; struct Tele{unsigned mapId;float position_x,position_y,position_z,orientation;std::string name;}; struct Objects{Tele alliance{0,-8833,622,94,0,"Stormwind"},horde{1,1570,-4397,16,0,"Orgrimmar"}; Tele*GetGameTeleExactName(std::string const&name){return name=="Stormwind"?&alliance:&horde;}}objects;auto sObjectMgr=&objects; namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}} struct Areas{bool AllowsPosition(Player*,unsigned,Position const&){return true;}}areas;auto sAdventureAreaMgr=&areas; enum class ClientlessActionExecutionState{Idle,Moving,Failed}; struct Status{ClientlessActionExecutionState State=ClientlessActionExecutionState::Failed;}; struct Executor{Status status;int clears=0;Status*GetStatus(unsigned){return &status;}void Clear(unsigned){++clears;status.State=ClientlessActionExecutionState::Idle;}}executor;auto sClientlessActionExecutorMgr=&executor; struct Core{void ClearQueuedActions(unsigned){}}core;auto sClientlessPlayerMgr=&core; struct Travel{void CancelTravel(unsigned){}}travel;auto sClientlessTravelMgr=&travel; struct AdventureBehaviorMgr{ enum class Phase{Idle,Travel,OnSite}; struct Record{time_t StalledSince=100,LastRescue=0,RescueAt=0,LastNavigationCheck=0;unsigned RescueFailures=5,ProgressMap=1;bool LastTravelFailed=true,Queued=true,Dirty=false;Position ProgressPosition,RescueOrigin;Phase State=Phase::Travel;std::string Detail;struct{bool Active=false;}Instance;}; void ResetMicro(Player*,Record&){}void FinishInstance(Player*,Record&r){r.Instance.Active=false;} bool UpdateNavigationRescue(Player*,Record&); }; '''+s[a:b]+r''' int main(){AdventureBehaviorMgr mgr;Player p;AdventureBehaviorMgr::Record r; GameTime::now=699;assert(!mgr.UpdateNavigationRescue(&p,r)&&p.teleports==0); GameTime::now=700;assert(mgr.UpdateNavigationRescue(&p,r)&&p.visual==1&&p.emote==193&&p.teleports==0); GameTime::now=709;assert(mgr.UpdateNavigationRescue(&p,r)&&p.teleports==0); GameTime::now=710;assert(mgr.UpdateNavigationRescue(&p,r)&&p.teleports==1&&p.mapId==1&&p.saves==1&&p.emote==0&&r.Dirty); assert(r.LastRescue==710); // Alliance capital, never the character's personal homebind. p={};p.alliance=true;r={};executor.status.State=ClientlessActionExecutionState::Failed; GameTime::now=700;assert(mgr.UpdateNavigationRescue(&p,r));GameTime::now=710;assert(mgr.UpdateNavigationRescue(&p,r)&&p.mapId==0); // Movement/combat interrupts the animation before it can teleport. p={};r={};executor.status.State=ClientlessActionExecutionState::Failed;GameTime::now=700; assert(mgr.UpdateNavigationRescue(&p,r));p.x=2;GameTime::now=701;assert(!mgr.UpdateNavigationRescue(&p,r)&&!r.RescueAt&&p.teleports==0); p={};r={};GameTime::now=700;assert(mgr.UpdateNavigationRescue(&p,r));p.combat=true;GameTime::now=710; assert(!mgr.UpdateNavigationRescue(&p,r)&&!r.RescueAt&&p.teleports==0); // Long gaps due to other activities cannot count as stalled navigation. p={};r={};r.LastNavigationCheck=100;GameTime::now=700; assert(!mgr.UpdateNavigationRescue(&p,r)&&p.visual==0&&r.StalledSince==700); // Native taxi/transport waits receive thirty minutes, rather than ten. p={};p.taxi=true;r={};GameTime::now=1899;assert(!mgr.UpdateNavigationRescue(&p,r)); r.LastNavigationCheck=0;GameTime::now=1900;assert(mgr.UpdateNavigationRescue(&p,r)); GameTime::now=1910;assert(mgr.UpdateNavigationRescue(&p,r)&&p.teleports==1&&!p.taxi); } ''' with tempfile.TemporaryDirectory()as d: p=Path(d);(p/'test.cpp').write_text(code) subprocess.run(['c++','-std=c++17','-I'+str(root/'src/server/game/World'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) subprocess.run([str(p/'test')],check=True) print('Real hearth recovery: grace, animation, faction capitals, cancellation, activity gaps and long taxi grace passed')