Files
Viano-Core/tests/clientless/test_adventure_navigation_rescue.py
T

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('\nbool AdventureBehaviorMgr::UpdateGathering(',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 &map;}
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')