144 lines
8.9 KiB
Python
144 lines
8.9 KiB
Python
"""Exercise real departure admission/ground relocation against layered terrain."""
|
|
from pathlib import Path
|
|
import subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
s=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
|
|
a=s.index('uint32 existingMount=0;');b=s.index(' bool foundLanding',a)
|
|
admission=s[a:b]
|
|
a=s.index('bool ClientlessFlightMgr::ApproachDeparture(');b=s.index('\nvoid ClientlessFlightMgr::Fail(',a)
|
|
approach=s[a:b]
|
|
a=s.index(' bool OpenColumn(');b=s.index(' char const * StateName',a)
|
|
column=s[a:b]
|
|
a=s.index(' float support=',s.index('bool ClientlessFlightMgr::Begin('));b=s.index(' auto const * profile =',a)
|
|
preparation=s[a:b]
|
|
a=s.index(' if(!existingMount)\n');b=s.index(' record.Status.State = ClientlessFlightState::Mounting;',a)
|
|
dismount=s[a:b]
|
|
a=s.index(' auto floor = sample(current.X, current.Y);');b=s.index(' player->StopMoving();',a)
|
|
touchdown=s[a:b]
|
|
a=s.index(' FindReturnLanding(player,record);');b=s.index(' record.Status.MountSpell=existingMount;',a)
|
|
return_anchor=s[a:b]
|
|
a=s.index('bool ClientlessFlightMgr::FindReturnLanding(');b=s.index('bool ClientlessFlightMgr::ApproachDeparture(',a)
|
|
find_return=s[a:b]
|
|
code=r'''
|
|
#include "ClientlessFlightPath.h"
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <cstdint>
|
|
using uint32=unsigned;using namespace ClientlessFlightPath;
|
|
#define TC_LOG_WARN(...) ((void)0)
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
constexpr float INVALID_HEIGHT=-100000;constexpr unsigned SPELL_AURA_MOUNTED=1;
|
|
struct Position{float x=0,y=0,z=0,o=0;
|
|
float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;}
|
|
void Relocate(float a,float b,float c,float d){x=a;y=b;z=c;o=d;}};
|
|
struct Guid{unsigned GetCounter()const{return 84;}};
|
|
struct PositionFullTerrainStatus{bool outdoors=true;};
|
|
struct Map{bool outdoors=true;void GetFullTerrainStatusForPosition(int,float,float,float,PositionFullTerrainStatus&t){t.outdoors=outdoors;}float feet=0;float GetHeight(int,float,float,float,bool,float){return feet;}}map;
|
|
struct Player:Position{bool mounted=false,fly=false,aura=false;int removals=0;
|
|
Guid GetGUID(){return {};}unsigned GetMapId(){return 530;}int GetPhaseShift(){return 0;}Map* GetMap(){return ↦}
|
|
bool outdoor=true;bool IsOutdoors(){return outdoor;}bool IsMounted(){return mounted;}bool CanFly(){return fly;}bool HasAura(unsigned){return aura;}
|
|
void RemoveAurasByType(unsigned){++removals;mounted=fly=false;}};
|
|
Point PointOf(Position const&p){return {p.x,p.y,p.z};}
|
|
enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed};
|
|
struct Record{struct{Position Target;ClientlessFlightState State=ClientlessFlightState::Failed;bool FollowingPlayer=false;std::string Detail;}Status;
|
|
bool ApproachingDeparture=false;Position Departure,Start;ClientlessFlightPath::Settings Settings;};
|
|
struct ClientlessPathDiagnostics{bool Calculated=true,Complete=true,Suspicious=false;float FinalDistance=0,PathLength=15,MaxSegmentLength=4,MaxZDelta=1;};
|
|
struct Travel{ClientlessPathDiagnostics nav;bool start=true;int starts=0,probes=0;
|
|
bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics& d){++probes;d=nav;return true;}
|
|
bool StartTravel(Player*,Position const&,bool){++starts;return start;}}travel;
|
|
auto sClientlessTravelMgr=&travel;
|
|
struct Areas{bool allowed=true;bool AllowsSegment(Player*,unsigned,Position const&,Position const&){return allowed;}}areas;
|
|
auto sAdventureAreaMgr=&areas;
|
|
struct Lua{int calls=0;void DismountGroundMount(Player*,char const*){++calls;}}lua;
|
|
auto sClientlessLuaEngine=&lua;
|
|
bool clear=true,narrow=false,columnBlocked=false;
|
|
bool ClearRay(Player*,Point const&a,Point const&b){
|
|
if(!clear||(narrow&&std::fabs(a.Y)>=0.01f))return false;
|
|
if(columnBlocked)for(int i=0;i<=80;++i){auto p=Mix(a,b,i/80.f);if(std::fabs(p.X)<1&&std::fabs(p.Y)<1&&p.Z>3&&p.Z<5)return false;}
|
|
return true;
|
|
}
|
|
'''+column+r'''
|
|
struct ClientlessFlightMgr{
|
|
std::unordered_map<unsigned,Record>_records;Surface surface{0,300,true};int approached=0;bool seek=false;
|
|
Surface Sample(Player*,Record&,float,float){return surface;}
|
|
bool ApproachDeparture(Player*,Record&,bool);bool FindReturnLanding(Player*,Record&);
|
|
bool Prepare(Player*player,Record&record,std::string&error){auto origin=surface;bool following=false;
|
|
'''+preparation+r'''
|
|
return false;}
|
|
bool Admit(Player* player,Record&record,std::string&error,bool following=false){
|
|
auto origin=surface;std::vector<std::pair<unsigned,unsigned>>candidates{{123,1}};
|
|
'''+admission+r'''
|
|
return true;
|
|
}
|
|
void ReturnAnchor(Player*player,Record&record){auto origin=surface;
|
|
'''+return_anchor+r'''
|
|
}
|
|
void Fail(Player*,Record&,std::string const&){}
|
|
bool Touchdown(Player*player){auto current=PointOf(*player);Record record;auto sample=[&](float,float){return surface;};
|
|
'''+touchdown+r'''
|
|
return true;
|
|
}
|
|
void KeepMount(Player*player,bool airborne){uint32 existingMount=airborne?123:0;
|
|
'''+dismount+r'''
|
|
}
|
|
};
|
|
'''+find_return+approach+r'''
|
|
int main(){
|
|
ClientlessFlightMgr mgr;Player p;Record r;std::string error;r.Status.Target={100,0,0};
|
|
// An indoor actor may walk to a verified outdoor spot before mounting.
|
|
p.outdoor=false;assert(mgr.Prepare(&p,r,error)&&mgr._records.at(84).ApproachingDeparture);
|
|
mgr._records.clear();travel.starts=0;map.outdoors=false;
|
|
assert(!mgr.Prepare(&p,r,error)&&travel.starts==0);map.outdoors=true;p.outdoor=true;
|
|
// Open ground beyond the old 40-yard ring remains usable, with a bounded MMAP leg.
|
|
travel.nav.PathLength=200;assert(mgr.ApproachDeparture(&p,r,false));
|
|
travel.nav.PathLength=15;travel.starts=0;mgr._records.clear();
|
|
// A normal supported open-air departure remains valid.
|
|
assert(mgr.Admit(&p,r,error));
|
|
// Brynli: a native flying mount already suspended ten yards above terrain.
|
|
p.z=10;p.mounted=p.fly=p.aura=true;map.feet=INVALID_HEIGHT;
|
|
assert(mgr.Admit(&p,r,error));assert(mgr._records.empty());
|
|
r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.z==0);
|
|
narrow=true;r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.z==10);narrow=false;
|
|
columnBlocked=true;r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.x>1&&r.Start.z==0&&p.x==0&&p.z==10);
|
|
areas.allowed=false;r.Start=p;mgr.ReturnAnchor(&p,r);assert(r.Start.z==10);areas.allowed=true;columnBlocked=false;
|
|
mgr.KeepMount(&p,true);assert(p.mounted&&p.fly&&p.removals==0&&lua.calls==0);
|
|
// Airborne recovery never applies to an unmounted/ground-only bot.
|
|
p.fly=false;assert(!mgr.Admit(&p,r,error));assert(error.find("departure-unavailable:")==0);
|
|
p.fly=true;p.aura=false;assert(!mgr.Admit(&p,r,error));p.aura=true;
|
|
clear=false;assert(!mgr.Admit(&p,r,error));clear=true;
|
|
mgr.surface.Ceiling=12;assert(!mgr.Admit(&p,r,error));mgr.surface.Ceiling=300;
|
|
// Tholgar: grounded on a lower layer, while the sky sample sees an overhead floor.
|
|
p.z=0;p.mounted=p.fly=p.aura=false;map.feet=0;mgr.surface.Ground=12;
|
|
assert(mgr.Admit(&p,r,error));auto saved=mgr._records.at(84);
|
|
assert(saved.ApproachingDeparture&&saved.Status.State==ClientlessFlightState::Mounting);
|
|
assert(saved.Status.Target.x==100&&p.x==0&&p.z==0&&travel.starts==1);
|
|
// A roof, forbidden ground corridor, partial MMAP path, cliff or failed movement
|
|
// must never manufacture a departure or move the player through the obstruction.
|
|
mgr._records.clear();travel.starts=0;clear=false;
|
|
assert(!mgr.Admit(&p,r,error)&&mgr._records.empty()&&travel.starts==0);clear=true;
|
|
narrow=true;assert(!mgr.Admit(&p,r,error));narrow=false;
|
|
areas.allowed=false;assert(!mgr.Admit(&p,r,error));areas.allowed=true;
|
|
travel.nav.Complete=false;assert(!mgr.Admit(&p,r,error));travel.nav.Complete=true;
|
|
travel.nav.MaxZDelta=20;assert(!mgr.Admit(&p,r,error));travel.nav.MaxZDelta=1;
|
|
travel.nav.PathLength=601;assert(!mgr.Admit(&p,r,error));travel.nav.PathLength=15;
|
|
travel.start=false;assert(!mgr.Admit(&p,r,error));travel.start=true;
|
|
map.feet=INVALID_HEIGHT;assert(!mgr.Admit(&p,r,error));
|
|
mgr.surface.Ground=0;map.feet=NAN;assert(!mgr.Admit(&p,r,error));map.feet=0;mgr.surface.Ground=12;
|
|
// A touchdown may use the actual lower floor, never a roof above it.
|
|
map.feet=0;p.z=0;mgr.surface.Ground=12;
|
|
assert(OpenColumn(&p,0,0,{0,300,true}));narrow=true;assert(!OpenColumn(&p,0,0,{0,300,true}));narrow=false;
|
|
assert(!OpenColumn(&p,0,0,{0,9,true}));assert(!OpenColumn(&p,0,0,{0,300,false}));
|
|
assert(mgr.Touchdown(&p));
|
|
p.z=1.2f;assert(!mgr.Touchdown(&p));p.z=0;
|
|
map.feet=INVALID_HEIGHT;assert(!mgr.Touchdown(&p));map.feet=0;
|
|
clear=false;assert(!mgr.Touchdown(&p));clear=true;
|
|
mgr.KeepMount(&p,false);assert(p.removals==1&&lua.calls==1);
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-flight-departure-') as d:
|
|
p=Path(d);(p/'test.cpp').write_text(code)
|
|
subprocess.run(['c++','-include',str(Path(__file__).with_name('telemetry_test_stub.h')),'-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('Production flight admission: airborne mount retained, lower-floor MMAP departure, blocked/cliff/missing support rejected.')
|