Add local deck navigation for AdventureBot transport passengers
This commit is contained in:
1 parent
49712aadfe
commit
2841ee973b
19 files changed
+693
-5
No files matched your search
@@ -100,6 +100,7 @@ public:
|
||||
struct Node{} node;
|
||||
struct Graph{Node* FindNode(std::string const&){return &node;}}graph;auto sClientlessTravelGraphMgr=&graph;
|
||||
struct AreaAccess{bool allowed=true;bool Applies(Player*){return true;}bool AllowsNode(Player*,Node const&){return allowed;}}areas;auto sAdventureAreaMgr=&areas;
|
||||
namespace ClientlessTransportDeckMgr {bool ready=true;int calls=0;void Clear(uint32){} bool Update(Player*,GameObject*,Position const&,bool,uint32){++calls;return ready;}}
|
||||
namespace ClientlessPositioning{bool available=false;bool Nearby(Player*p,Position const&,float,Position&spot,std::vector<Position>&path){if(!available)return false;spot=*p;spot.x+=2;path={*p,spot};return true;}}
|
||||
void TransportBase::CalculatePassengerPosition(float&x,float&y,float&z,float*o)const{CalculatePassengerPosition(x,y,z,o,go->x,go->y,go->z,go->o);}
|
||||
void TransportBase::CalculatePassengerOffset(float&x,float&y,float&z,float*o)const{CalculatePassengerOffset(x,y,z,o,go->x,go->y,go->z,go->o);}
|
||||
@@ -177,7 +178,9 @@ int main(){
|
||||
go.state.RemainingPause=10000;run(mgr,p,r);assert(r.Status.Phase==Phase::Riding&&p.transport==&go.base);assert(std::fabs(p.m_movementInfo.transport.pos.x-1)<0.01);
|
||||
// Wrong location despite a stopped object must not allow disembarkation.
|
||||
go.x=50;p.x=51;run(mgr,p,r);assert(r.Status.Phase==Phase::Riding&&p.transport);
|
||||
go.x=100;p.x=101;run(mgr,p,r);assert(r.Status.Phase==Phase::Arrived&&!p.transport&&p.x==104);
|
||||
go.x=100;p.x=101;ClientlessTransportDeckMgr::ready=false;
|
||||
run(mgr,p,r);assert(r.Status.Phase==Phase::Riding&&p.transport);
|
||||
ClientlessTransportDeckMgr::ready=true;run(mgr,p,r);assert(r.Status.Phase==Phase::Arrived&&!p.transport&&p.x==104);
|
||||
// Cancellation on land does not attach a passenger.
|
||||
p.x=0;go.x=5;assert(mgr.Start(&p,"A","B",error));mgr.Cancel(26);auto&cancel=mgr._rides.at(26);run(mgr,p,cancel);assert(cancel.Status.Phase==Phase::Failed&&cancel.Status.Cancelled&&!p.transport);
|
||||
// Midride cancellation retains the Passenger until a recorded safe dock.
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
"""Run the actual deck controller against deterministic docking/movement doubles."""
|
||||
from pathlib import Path
|
||||
import subprocess,tempfile
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
s=(root/'src/server/game/World/ClientlessTransportDeckMgr.cpp').read_text()
|
||||
a=s.index('bool ClientlessTransportDeckMgr::Update(');b=s.index('\nvoid ClientlessTransportDeckMgr::Diagnose(',a)
|
||||
code=r'''
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <list>
|
||||
#include <unordered_map>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <ctime>
|
||||
#include <cassert>
|
||||
#include <iostream>
|
||||
using uint32=unsigned;using uint64=unsigned long long;using Point=std::array<float,3>;
|
||||
using ObjectGuid=unsigned;
|
||||
struct Creature;
|
||||
struct Position{float x=0,y=0,z=0;float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}};
|
||||
struct TransportDockState{bool Stopped=true;uint32 RemainingPause=60000;};
|
||||
struct GameObject{TransportDockState dock;unsigned entry=176310;unsigned GetEntry(){return entry;}unsigned GetGUID(){return 123;}float GetObjectScale(){return 1;}bool GetTransportDockState(TransportDockState& d){d=dock;return true;}}object;
|
||||
struct Guid{unsigned id;unsigned GetCounter(){return id;}};
|
||||
struct Player{Point current{0,0,0};unsigned id=1;bool spline=false,alive=true,combat=false;int stops=0;void* transport=&object;
|
||||
Guid GetGUID(){return {id};}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsSplineEnabled(){return spline;}
|
||||
void StopMoving(){++stops;spline=false;}void* GetTransport(){return transport;}void GetPlayerListInGrid(std::list<Player*>&,int){} void GetCreatureListWithEntryInGrid(std::list<Creature*>&,int,int){}};
|
||||
struct Creature:Player{};
|
||||
struct Passenger{unsigned Transport=0;bool Tried=false,Moving=false,Reserved=false;Point Target{};time_t Deadline=0;};
|
||||
std::unordered_map<unsigned,Passenger> Passengers;
|
||||
float Distance(Point const&a,Point const&b){return std::sqrt(std::pow(a[0]-b[0],2)+std::pow(a[1]-b[1],2)+std::pow(a[2]-b[2],2));}
|
||||
Point Local(Player*p){return p->current;}float Length(std::vector<Point>const&path){float n=0;for(unsigned i=1;i<path.size();++i)n+=Distance(path[i-1],path[i]);return n;}
|
||||
struct Mesh{bool blocked=false;unsigned Polygons(){return 50;}bool Path(Point const&a,Point const&b,std::vector<Point>&p){p={a,b};return !blocked;}};
|
||||
struct Model{Mesh Mesh;}model;
|
||||
Model* Get(unsigned entry){return entry==176310?&model:nullptr;}
|
||||
namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}}
|
||||
int launches=0;
|
||||
void Launch(Player*p,Passenger&s,std::vector<Point>const&path){++launches;p->spline=true;s.Moving=true;s.Target=path.back();s.Deadline=GameTime::now+20;}
|
||||
namespace ClientlessTransportDeckMgr{bool Update(Player*,GameObject*,Position const&,bool,uint32);}
|
||||
'''+s[a:b]+r'''
|
||||
int main(){Player p;Position anchor;
|
||||
// Insufficient halt time never starts a walk.
|
||||
object.dock.RemainingPause=1000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(!launches);
|
||||
// Departure does not need time for disembarkation at this station.
|
||||
Passengers.clear();object.dock.RemainingPause=4500;
|
||||
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(launches==1);
|
||||
Passengers.clear();p.spline=false;launches=0;object.dock.RemainingPause=60000;
|
||||
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(launches==1&&Passengers[1].Moving);
|
||||
// Departure stops the spline but keeps ownership.
|
||||
object.dock.Stopped=false;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(p.stops==1&&!p.spline&&p.transport==&object);
|
||||
// Arriving at the next dock starts a real return, provided it fits the halt.
|
||||
p.current={4,0,0};object.dock.Stopped=true;object.dock.RemainingPause=6000;
|
||||
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==1);
|
||||
object.dock.RemainingPause=60000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2);
|
||||
assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000)); // still moving
|
||||
p.spline=false;p.current={0,0,0};assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));
|
||||
// Interrupted arrival never claims the exit anchor was reached.
|
||||
p.current={4,0,0};model.Mesh.blocked=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));assert(launches==2);
|
||||
model.Mesh.blocked=false;p.combat=true;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));p.combat=false;
|
||||
Passengers.clear();p.current={0,0,0};p.spline=false;launches=0;
|
||||
ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000);Point first=Passengers[1].Target;
|
||||
Player other;other.id=2;ClientlessTransportDeckMgr::Update(&other,&object,anchor,false,5000);
|
||||
assert(launches==2&&Distance(first,Passengers[2].Target)>=1.5f);
|
||||
// Near departure, a running walk is stopped on the deck.
|
||||
object.dock.RemainingPause=1000;assert(!ClientlessTransportDeckMgr::Update(&p,&object,anchor,false,5000));assert(!p.spline);
|
||||
object.entry=999;assert(ClientlessTransportDeckMgr::Update(&p,&object,anchor,true,5000));
|
||||
std::cout<<"Production deck controller: halt budgets, departure interruption, return-before-exit, blocked paths and reservations passed\n";
|
||||
}
|
||||
'''
|
||||
# Avoid a type/member name collision in the standalone double.
|
||||
code=code.replace('struct Mesh{','struct FakeMesh{').replace('struct Model{Mesh Mesh;}','struct Model{FakeMesh Mesh;}')
|
||||
with tempfile.TemporaryDirectory()as d:
|
||||
p=Path(d);(p/'test.cpp').write_text(code)
|
||||
subprocess.run(['c++','-std=c++17',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
|
||||
subprocess.run([str(p/'test')],check=True)
|
||||
@@ -0,0 +1,65 @@
|
||||
"""Compile the real local mesh builder and test synthetic and extracted vehicle geometry."""
|
||||
from pathlib import Path
|
||||
import subprocess, tempfile, struct, argparse
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
parser=argparse.ArgumentParser();parser.add_argument('--data',default='/home/wow_admin/wow_cata_server/data/vmaps');parser.add_argument('--build',default='/home/wow_admin/wow_cata_server/build/debug');args=parser.parse_args()
|
||||
code=r'''
|
||||
#include "TransportDeckMesh.h"
|
||||
#include <cassert>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <cmath>
|
||||
using P=TransportDeckMesh::Point;
|
||||
void quad(std::vector<float>& v,std::vector<int>& t,float x,float y,float z,float w,float h){int i=v.size()/3;
|
||||
for(auto p:{P{x,y,z},P{x+w,y,z},P{x+w,y+h,z},P{x,y+h,z}})v.insert(v.end(),p.begin(),p.end());
|
||||
t.insert(t.end(),{i,i+1,i+2,i,i+2,i+3});}
|
||||
void wall(std::vector<float>& v,std::vector<int>& t,float x,float y0,float y1){int i=v.size()/3;
|
||||
for(auto p:{P{x,y0,-1},P{x,y1,-1},P{x,y1,4},P{x,y0,4}})v.insert(v.end(),p.begin(),p.end());
|
||||
t.insert(t.end(),{i,i+1,i+2,i,i+2,i+3});}
|
||||
int main(int argc,char**argv){TransportDeckMesh mesh;std::string error;std::vector<float> v;std::vector<int> t;std::vector<P> path;
|
||||
quad(v,t,-5,-5,0,10,10);quad(v,t,12,-5,0,10,10);quad(v,t,-5,-5,5,10,10);
|
||||
assert(mesh.Build(v,t,error));assert(mesh.Path({0,0,0},{3,3,0},path));
|
||||
assert(!mesh.Path({0,0,0},{15,0,0},path)); // disconnected deck
|
||||
assert(!mesh.Path({0,0,0},{0,0,5},path)); // cannot jump floors
|
||||
assert(mesh.Path({0,0,0},{5,0,0},path) && path.back()[0]<4.5f); // snap stays inside eroded edge
|
||||
assert(!mesh.Path({0,0,0},{6,0,0},path));
|
||||
assert(!mesh.Path({0,0,0},{0,0,2},path)); // no projection to another floor
|
||||
assert(!mesh.Path({NAN,0,0},{0,0,0},path));
|
||||
v.clear();t.clear();quad(v,t,-8,-8,0,16,16);wall(v,t,0,-8,8);
|
||||
assert(mesh.Build(v,t,error));assert(!mesh.Path({-3,0,0},{3,0,0},path)); // solid wall
|
||||
v.clear();t.clear();quad(v,t,-8,-8,0,16,16);wall(v,t,0,-8,4);
|
||||
assert(mesh.Build(v,t,error));assert(mesh.Path({-3,0,0},{3,0,0},path));
|
||||
bool around=false;for(auto const& point:path)if(point[1]>4)around=true;assert(around); // routed around obstruction
|
||||
v.clear();t.clear();quad(v,t,-5,-5,0,10,10);quad(v,t,-1,-5,1.3f,2,10);
|
||||
assert(mesh.Build(v,t,error));assert(!mesh.Path({-3,0,0},{3,0,0},path)); // insufficient headroom
|
||||
for(int a=1;a<argc;++a){std::ifstream f(argv[a],std::ios::binary);unsigned nv,nt;f.read((char*)&nv,4);f.read((char*)&nt,4);
|
||||
v.resize(nv);t.resize(nt);f.read((char*)v.data(),nv*4);f.read((char*)t.data(),nt*4);P seed;f.read((char*)seed.data(),12);
|
||||
assert(mesh.Build(v,t,error));assert(mesh.Path(seed,seed,path));int reachable=0,returns=0;float longest=0;
|
||||
for(float x=-10;x<=10;x+=1)for(float y=-10;y<=10;y+=1){P p{seed[0]+x,seed[1]+y,seed[2]};
|
||||
if(mesh.Path(seed,p,path)){++reachable;std::vector<P> back;if(mesh.Path(path.back(),seed,back))++returns;}}
|
||||
std::cout<<argv[a]<<" polygons="<<mesh.Polygons()<<" reachable="<<reachable<<" returns="<<returns<<"\n";
|
||||
assert(reachable>8 && returns==reachable);
|
||||
}
|
||||
assert(!mesh.Build({},t,error));std::cout<<"Real deck meshes: connectivity, floor isolation, edge clearance and return paths passed\n";
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
p=Path(d);fixtures=[]
|
||||
for name,seed,m2 in [('Transportship.wmo',(-1.87779,7.8222,6.21784),False),('Subwaycar.m2',(-.112158,-.187065,-10.468),True)]:
|
||||
data=(Path(args.data)/(name+'.vmo')).read_bytes();start=0;verts=[];tris=[]
|
||||
while True:
|
||||
q=data.find(b'VERT',start)
|
||||
if q<0:break
|
||||
size,n=struct.unpack_from('<II',data,q+4)
|
||||
if size!=4+n*12:start=q+4;continue
|
||||
end=q+8+size
|
||||
if data[end:end+4]!=b'TRIM':start=end;continue
|
||||
ts,nt=struct.unpack_from('<II',data,end+4);offset=len(verts)//3
|
||||
verts.extend(struct.unpack_from('<'+str(n*3)+'f',data,q+12))
|
||||
for ids in struct.iter_unpack('<3I',data[end+12:end+12+nt*12]):
|
||||
a,b,c=ids;tris.extend((offset+a,offset+c,offset+b) if m2 else (offset+a,offset+b,offset+c))
|
||||
start=end+8+ts
|
||||
f=p/(name+'.fixture');f.write_bytes(struct.pack('<II',len(verts),len(tris))+struct.pack('<'+str(len(verts))+'f',*verts)+struct.pack('<'+str(len(tris))+'i',*tris)+struct.pack('<3f',*seed));fixtures.append(str(f))
|
||||
(p/'test.cpp').write_text(code)
|
||||
subprocess.run(['c++','-std=c++17','-O1','-I'+str(root/'src/server/game/World'),'-I'+str(root/'dep/recastnavigation/Recast/Include'),'-I'+str(root/'dep/recastnavigation/Detour/Include'),str(p/'test.cpp'),str(root/'src/server/game/World/TransportDeckMesh.cpp'),str(Path(args.build)/'dep/recastnavigation/Recast/libRecast.a'),str(Path(args.build)/'dep/recastnavigation/Detour/libDetour.a'),'-o',str(p/'test')],check=True)
|
||||
subprocess.run([str(p/'test'),*fixtures],check=True)
|
||||
Reference in new issue
Block a user