79 lines
6.0 KiB
Python
79 lines
6.0 KiB
Python
"""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 <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <ctime>
|
|
#include <string>
|
|
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')
|