Fix AdventureBot group travel and add explicit tram rescue routes
This commit is contained in:
1 parent
54d77d1e4d
commit
19075b6c75
32 files changed
+1211
-60
No files matched your search
@@ -10,6 +10,7 @@ code=r'''
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
using uint32=unsigned;
|
||||
#define TC_LOG_INFO(...) ((void)0)
|
||||
struct Position{float x=0;};struct Guid{unsigned GetCounter(){return 26;}};
|
||||
@@ -17,10 +18,14 @@ struct Player{float x=50;unsigned map=0;bool combat=false;unsigned stopped=0;Gui
|
||||
float GetExactDist(Position const&p){return std::fabs(x-p.x);}void StopMoving(){++stopped;}};
|
||||
bool Unsafe(Player*p){return p->combat;}
|
||||
enum class ClientlessTravelState{Moving,Arrived,Rejected};
|
||||
struct Travel{struct{ClientlessTravelState State=ClientlessTravelState::Rejected;}status;unsigned cancels=0;auto GetStatus(unsigned){return &status;}void CancelTravel(unsigned){++cancels;status.State=ClientlessTravelState::Arrived;}}travel;
|
||||
struct Travel{struct{ClientlessTravelState State=ClientlessTravelState::Rejected;}status;unsigned cancels=0;bool StartTravel(Player*,Position const&){status.State=ClientlessTravelState::Moving;return true;}auto GetStatus(unsigned){return &status;}void CancelTravel(unsigned){++cancels;status.State=ClientlessTravelState::Arrived;}}travel;
|
||||
auto sClientlessTravelMgr=&travel;
|
||||
namespace ClientlessPositioning{float clearance=10;bool available=false;unsigned reserves=0;
|
||||
float Clearance(Player*,Player const&){return clearance;}void Reserve(Player*,Player const&){++reserves;}
|
||||
bool Nearby(Player*,Position const&,float,Position&spot,std::vector<Position>&){spot.x=3;return available;}}
|
||||
|
||||
struct AdventureBehaviorMgr{enum class Phase{Travel,OnSite};struct Record{
|
||||
std::string Intent="groupformation",Micro="profession_tools",Detail;time_t Deadline=400,GatherRetryAt=0;
|
||||
std::string Intent="groupformation",Micro="profession_tools",Detail;time_t Deadline=400,GatherRetryAt=0,NextSpacing=0;
|
||||
bool Gathering=false,Dirty=false;Phase State=Phase::OnSite;unsigned AnchorMap=0,MicroTimer=20000;Position Anchor;
|
||||
struct{bool Active=false,StopRequested=false;}Leisure;
|
||||
struct{bool Active=true;}Social;struct{bool Active=false;}Recovery;
|
||||
@@ -48,6 +53,12 @@ int main(){
|
||||
assert(m.UpdateGathering(&p,r,112)&&p.stopped==1&&travel.cancels==1);
|
||||
assert(r.Detail=="waiting at group meeting point before departure");
|
||||
auto n=m.moves;assert(m.UpdateGathering(&p,r,399)&&m.moves==n); // hold, no new action
|
||||
ClientlessPositioning::clearance=0;ClientlessPositioning::available=true;
|
||||
assert(m.UpdateGathering(&p,r,399)&&r.NextSpacing==429&&travel.status.State==ClientlessTravelState::Moving);
|
||||
auto cancels=travel.cancels;
|
||||
assert(m.UpdateGathering(&p,r,399)&&travel.cancels==cancels); // spacing walk must finish
|
||||
p.x=3;travel.status.State=ClientlessTravelState::Arrived;
|
||||
assert(m.UpdateGathering(&p,r,399)&&travel.cancels==cancels);
|
||||
assert(!m.UpdateGathering(&p,r,400)&&!r.Gathering&&r.GatherRetryAt==0); // deadline never waits for members
|
||||
r={};r.State=AdventureBehaviorMgr::Phase::Travel;assert(!m.UpdateGathering(&p,r,100)&&!r.Gathering);
|
||||
r.State=AdventureBehaviorMgr::Phase::OnSite;r.Dirty=true;assert(!m.UpdateGathering(&p,r,100));
|
||||
|
||||
@@ -46,7 +46,7 @@ struct Player:Position{
|
||||
ObjectGuid guid;bool alive=true,combat=false,teleport=false,charmed=false,flying=false,falling=false,mounted=false,moving=false,taxi=false,transport=false,phase=true,visible=true;unsigned mapid=0;Group*g=&group;Session session;
|
||||
Player(unsigned id):guid{id}{}ObjectGuid GetGUID(){return guid;}Group*GetGroup(){return g;}Map*GetMap(){return ↦}unsigned GetMapId(){return mapid;}unsigned GetPhaseShift(){return 169;}
|
||||
Session*GetSession(){return &session;}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool IsCharmed(){return charmed;}
|
||||
bool IsFlying(){return flying;}bool IsFalling(){return falling;}bool IsMounted(){return mounted;}bool isMoving(){return moving;}bool IsInFlight(){return taxi;}bool GetTransport(){return transport;}
|
||||
bool CanFly(){return mounted && flightCapable;}bool flightCapable=false;bool IsFlying(){return flying;}bool IsFalling(){return falling;}bool IsMounted(){return mounted;}bool isMoving(){return moving;}bool IsInFlight(){return taxi;}bool GetTransport(){return transport;}
|
||||
bool IsInMap(Player*p){return mapid==p->mapid;}bool IsInPhase(Player*p){return phase && p->phase;}bool CanSeeOrDetect(Player*p){return visible && p->visible;}bool IsInAreaTriggerRadius(void*){return false;}
|
||||
};
|
||||
struct WorldPacket{WorldPacket(int,int){}template<class T>WorldPacket&operator<<(T){return *this;}};
|
||||
@@ -71,7 +71,7 @@ enum class ClientlessTransportPhase{Waiting,Riding,Arrived,Failed};
|
||||
struct TransportStatus{ClientlessTransportPhase Phase=ClientlessTransportPhase::Failed;};struct Stop{unsigned Map=0;struct Point{Position Pos;};std::vector<Point>Points{{{0,0,0}}};};
|
||||
struct Transport{TransportStatus status;auto GetStatus(unsigned){return &status;}Stop*GetStop(std::string const&){return nullptr;}bool Start(Player*,std::string const&,std::string const&,std::string&){return false;}}transport;auto sClientlessTransportMgr=&transport;
|
||||
enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed};
|
||||
struct Flight{struct Status{ClientlessFlightState State=ClientlessFlightState::Failed;bool FollowingPlayer=false;}status;bool enabled=false;unsigned follows=0,lands=0;auto GetStatus(unsigned){return &status;}bool Enabled(Player*){return enabled;}
|
||||
struct Flight{struct Status{ClientlessFlightState State=ClientlessFlightState::Failed;bool FollowingPlayer=false;}status;bool enabled=false;unsigned follows=0,lands=0;auto GetStatus(unsigned){return &status;}bool Enabled(Player*){return enabled;}bool PrepareFollowingMount(Player*){return false;}
|
||||
bool Follow(Player*,Position const&,std::string&){++follows;status.State=ClientlessFlightState::Flying;status.FollowingPlayer=true;return true;}bool LandFollowing(Player*){++lands;status.FollowingPlayer=false;return true;}}flight;auto sClientlessFlightMgr=&flight;
|
||||
struct Profile{float GroundMountMinDistance=60;};struct Lua{Profile profile;auto GetAdventureProfile(unsigned){return &profile;}void PrepareGroundMount(Player*p,float,char const*){p->mounted=true;}void DismountGroundMount(Player*p,char const*){p->mounted=false;}}lua;auto sClientlessLuaEngine=&lua;
|
||||
struct AreaAccess{bool allowed=true;bool AllowsPosition(Player*,unsigned,Position const&){return allowed;}}access;auto sAdventureAreaMgr=&access;
|
||||
@@ -102,6 +102,20 @@ int main(){
|
||||
leader.flying=true;leader.m_positionZ=80;starts=travel.starts;mgr.Follow(&a);assert(travel.starts==starts);leader.flying=false;leader.m_positionZ=0;
|
||||
// A native-capable follower uses flight, and safely lands after a kick.
|
||||
flight.enabled=true;leader.flying=true;mgr.Follow(&a);assert(flight.follows==1 && mgr.Airborne(&a));a.g=nullptr;mgr.Follow(&a);assert(flight.lands==1);a.g=&group;flight.status.State=ClientlessFlightState::Arrived;leader.flying=false;flight.enabled=false;mgr.Follow(&a);
|
||||
// A stale owned ground catch-up route cannot suppress a visible-phase
|
||||
// human leader taking off, even beyond normal visual detection distance.
|
||||
leader.mounted=leader.flightCapable=leader.flying=true;leader.visible=false;
|
||||
flight.enabled=true;flight.status.State=ClientlessFlightState::Failed;
|
||||
mgr._records.at(26).RouteOwned=true;executor.status.State=ClientlessActionExecutionState::Moving;
|
||||
auto cleared=executor.clears;auto flown=flight.follows;
|
||||
mgr.Follow(&a);assert(executor.clears==cleared+1&&flight.follows==flown+1);
|
||||
// Matching a grounded flying mount must never be immediately undone by
|
||||
// the ground-follow dismount branch.
|
||||
leader.flying=false;leader.visible=true;a.mounted=a.flightCapable=true;
|
||||
flight.status.State=ClientlessFlightState::Arrived;leader.m_positionX=8;
|
||||
mgr.Follow(&a);assert(a.mounted);
|
||||
leader.mounted=leader.flightCapable=false;a.mounted=a.flightCapable=false;
|
||||
flight.enabled=false;leader.m_positionX=20;
|
||||
// Taxi journey and its arrival grace pause distance counting. Completion must
|
||||
// not protect unrelated later ground separation.
|
||||
auto& state=observed;state.Kind=AdventureGroupTravelMgr::Journey::Taxi;state.Generation=1;state.Began=GameTime::now;state.To="taxi_42";state.GoalMap=1;state.Goal={1000,0,0};leader.taxi=true;leader.m_positionX=900;
|
||||
@@ -120,6 +134,20 @@ int main(){
|
||||
// A blocked/unrecognized trip cannot grant unlimited separation protection.
|
||||
mgr._records.at(26).Pending.clear();state.Kind=AdventureGroupTravelMgr::Journey::None;state.Generation=4;leader.mapid=0;leader.m_positionX=1000;a.mapid=0;a.m_positionX=0;executor.status.State=ClientlessActionExecutionState::Completed;
|
||||
for(unsigned i=0;i<1200;++i){if(i%2==0)++GameTime::now;mgr.Observe(&a,500);}assert(mgr.Separated(&a));leader.m_positionX=10;mgr.Observe(&a,500);assert(!mgr.Separated(&a));
|
||||
// Nearby targets belonging to adjacent rows are distinct: a 2yd change
|
||||
// cannot reuse an old spline that ends at a different formation position.
|
||||
leader.m_positionX=20;leader.m_positionZ=0;leader.moving=false;
|
||||
travel.status.State=ClientlessTravelState::Arrived;mgr.Follow(&a);
|
||||
auto oldTarget=travel.status.Target;leader.m_positionX+=2;auto startsBefore=travel.starts;
|
||||
mgr.Follow(&a);assert(travel.starts==startsBefore+1&&travel.status.Target.GetExactDist(oldTarget)>1.9f);
|
||||
// A short stop during an executing route still preserves row distance.
|
||||
leader.m_positionX=6;leader.moving=false;executor.status.State=ClientlessActionExecutionState::Moving;
|
||||
std::vector<Position>shortRoad{{0,0,0},{2,0,0},{4,0,0},{6,0,0},{8,0,0}};
|
||||
assert(mgr.AdaptRoad(&a,shortRoad)&&shortRoad.back().GetPositionX()<6);
|
||||
// Boarding is released once the leader actually starts the native taxi.
|
||||
leader.taxi=true;shortRoad={{0,0,0},{2,0,0},{4,0,0},{6,0,0},{8,0,0}};
|
||||
assert(mgr.AdaptRoad(&a,shortRoad)&&shortRoad.back().GetPositionX()==8);
|
||||
leader.taxi=false;executor.status.State=ClientlessActionExecutionState::Completed;
|
||||
// Recorded roads are bounded and validated, never a spline through a cliff.
|
||||
std::vector<Position>road;for(unsigned x=0;x<=100;x+=10)road.emplace_back(x,0,0);leader.moving=false;map.cliff=true;assert(!mgr.AdaptRoad(&a,road));map.cliff=false;road.clear();for(unsigned x=0;x<=100;x+=10)road.emplace_back(x,0,0);assert(mgr.AdaptRoad(&a,road) && road.back().GetPositionX()<=60);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ a=s.index('bool ClientlessFlightMgr::ApproachDeparture(');b=s.index('\nvoid Clie
|
||||
approach=s[a:b]
|
||||
a=s.index(' bool OpenColumn(');b=s.index(' char const * StateName',a)
|
||||
column=s[a:b]
|
||||
a=s.index(' if(!airborne)\n');b=s.index(' record.Status.State = ClientlessFlightState::Mounting;',a)
|
||||
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]
|
||||
@@ -73,7 +73,7 @@ struct ClientlessFlightMgr{
|
||||
'''+touchdown+r'''
|
||||
return true;
|
||||
}
|
||||
void KeepMount(Player*player,bool airborne){
|
||||
void KeepMount(Player*player,bool airborne){uint32 existingMount=airborne?123:0;
|
||||
'''+dismount+r'''
|
||||
}
|
||||
};
|
||||
|
||||
@@ -56,6 +56,9 @@ struct PathGenerator {static inline unsigned probes=0;static inline bool availab
|
||||
std::string MakeLocalBridgeKey(std::string const&a,std::string const&b){return a+"->"+b;}
|
||||
'''
|
||||
cls='class ClientlessTravelGraphMgr { public: bool EnsureLoaded(){return true;}\n'+signature+'''
|
||||
bool _rescuePlanning=false;
|
||||
bool RescueNodeAllowed(ClientlessTravelNode const& n)const{return _rescuePlanning || n.Type!="rescue";}
|
||||
bool RescueEdgeAllowed(ClientlessTravelEdge const& e)const{return RescueNodeAllowed(_nodes.at(e.FromNode))&&RescueNodeAllowed(_nodes.at(e.ToNode));}
|
||||
ClientlessGraphProbeCache _positionProbes;std::unordered_map<std::string,std::vector<std::string>> _bridgeNeighborhoods;
|
||||
std::unordered_map<std::string,ClientlessTravelNode> _nodes;
|
||||
std::unordered_map<std::string,std::vector<ClientlessTravelEdge>> _edges;
|
||||
|
||||
@@ -37,7 +37,7 @@ struct ClientlessFlightMgr{
|
||||
Surface Sample(Player*,Record&,float,float){return {map.floor,ceiling,valid};}
|
||||
bool Begin(Player*,Position const&,std::string&,bool following){assert(following);++beginnings;return true;}
|
||||
void Fail(Player*,Record&r,std::string const&){++failures;r.Status.State=ClientlessFlightState::Failed;}
|
||||
bool Follow(Player*,Position const&,std::string&);bool LandFollowing(Player*);bool AdvanceFollowing(Player*,Record&);
|
||||
void HoldFollowing(Player*p,Record&r){if(r.Moving)p->StopMoving();r.Moving=false;}bool Follow(Player*,Position const&,std::string&);bool LandFollowing(Player*);bool AdvanceFollowing(Player*,Record&);
|
||||
};
|
||||
'''
|
||||
for sig in ['bool ClientlessFlightMgr::Follow(', 'bool ClientlessFlightMgr::LandFollowing(', 'bool ClientlessFlightMgr::AdvanceFollowing(']:code+=method(sig)+'\n'
|
||||
@@ -49,7 +49,7 @@ int main(){
|
||||
assert(mgr.AdvanceFollowing(&p,r));assert(p.launches==1 && r.Segment.x>0 && r.Segment.x<=25 && r.Segment.z==20);
|
||||
// Moving the leader updates the destination without inventing a ground landing.
|
||||
target.Relocate(110,20,30);r.Elapsed=2000;assert(mgr.Follow(&p,target,error));assert(r.Status.Target.z==30 && r.TargetUpdated==2000);
|
||||
clear=false;assert(!mgr.AdvanceFollowing(&p,r));assert(p.stops==1 && r.Status.State==ClientlessFlightState::Flying);
|
||||
clear=false;r.Moving=true;assert(!mgr.AdvanceFollowing(&p,r));assert(p.stops==1 && r.Status.State==ClientlessFlightState::Flying);
|
||||
clear=true;ceiling=25;assert(!mgr.Follow(&p,target,error));ceiling=300;
|
||||
p.Relocate(110,20,30);r.Moving=true;assert(mgr.AdvanceFollowing(&p,r));assert(!r.Moving && p.stops==2); // holds in air, never dismounts at moving goal
|
||||
assert(mgr.LandFollowing(&p));assert(!r.Status.FollowingPlayer && r.Status.Landing.z==0 && r.Status.Travelled==0);
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
"""Run native mount admission and stationary-flight publication adapters."""
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
import tempfile
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
s=(root/'src/server/game/World/ClientlessFlightMgr.cpp').read_text()
|
||||
def method(sig):
|
||||
a=s.index(sig);b=s.index('{',a);e=b+1;n=1
|
||||
while n:
|
||||
n+=(s[e]=='{')-(s[e]=='}');e+=1
|
||||
return s[a:e]
|
||||
code=r'''
|
||||
#include <algorithm>
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
using uint32=unsigned;using uint8=unsigned char;
|
||||
constexpr int MAP_COMMON=0,UNIT_STATE_CASTING=1,MOUNT_CAPABILITY_FLAG_FLYING=1,SPELL_AURA_MOUNTED=1,SPELL_CAST_OK=0;
|
||||
constexpr int MOVEMENTFLAG_FLYING=1,UNIT_FIELD_BYTES_1=1,UNIT_BYTES_1_OFFSET_ANIM_TIER=1,SMSG_MOVE_UPDATE=1;
|
||||
enum class AnimTier:uint8{Ground=0,Fly=3};
|
||||
struct Position{void Relocate(Position const&){}};
|
||||
struct Map{struct Entry{int MapType=MAP_COMMON;}entry;Entry*GetEntry(){return &entry;}}map;
|
||||
struct Guid{unsigned GetCounter(){return 49;}};
|
||||
struct WorldPacket{explicit WorldPacket(int){}};
|
||||
namespace GameTime{unsigned GetGameTimeMS(){return 100;}}
|
||||
struct Player{
|
||||
bool alive=true,combat=false,teleport=false,transport=false,taxi=false,falling=false,water=false,outdoors=true,mounted=true,fly=false,casting=false,learned=true;
|
||||
unsigned flags=0,tier=0,stops=0,updates=0,animations=0,removals=0,casts=0;int castResult=SPELL_CAST_OK;
|
||||
struct Capability{unsigned Flags=MOUNT_CAPABILITY_FLAG_FLYING;}capability;
|
||||
struct{Guid guid;Position pos;unsigned time=0;}m_movementInfo;
|
||||
bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return transport;}bool IsInFlight(){return taxi;}
|
||||
bool IsFalling(){return falling;}bool IsInWater(){return water;}bool IsOutdoors(){return outdoors;}Map*GetMap(){return ↦}bool IsMounted(){return mounted;}bool CanFly(){return fly;}
|
||||
bool HasUnitState(int){return casting;}unsigned GetZoneId(){return 1519;}Guid GetGUID(){return {};}
|
||||
bool HasSpell(unsigned){return learned;}Capability*GetMountCapability(unsigned){return &capability;}
|
||||
void StopMoving(){++stops;}void RemoveAurasByType(int){++removals;mounted=false;}
|
||||
int CastSpell(Player*,unsigned spell,bool triggered){assert(spell==123&&!triggered);++casts;return castResult;}
|
||||
bool HasUnitMovementFlag(int flag){return flags&flag;}void AddUnitMovementFlag(int flag){flags|=flag;}
|
||||
unsigned GetByteValue(int,int){return tier;}void SetAnimTier(AnimTier t){tier=unsigned(t);++animations;}
|
||||
Position GetPosition(){return {};}void WriteMovementInfo(WorldPacket&){assert(flags&MOVEMENTFLAG_FLYING);}
|
||||
void SendMessageToSet(WorldPacket*,bool){++updates;}
|
||||
};
|
||||
struct Battlefield{bool allowed=true;bool CanFlyIn(){return allowed;}}battle;
|
||||
struct BattleMgr{Battlefield*GetBattlefieldToZoneId(Map*,unsigned){return &battle;}}battleMgr;auto sBattlefieldMgr=&battleMgr;
|
||||
struct Profile{struct Mount{unsigned DisplayId,Weight;};std::vector<Mount>FlightMounts{{123,1}};}profile;
|
||||
struct Lua{Profile*GetAdventureProfile(unsigned){return &profile;}void DismountGroundMount(Player*,char const*){}}lua;auto sClientlessLuaEngine=&lua;
|
||||
struct Travel{unsigned cancels=0;void CancelTravel(unsigned){++cancels;}}travel;auto sClientlessTravelMgr=&travel;
|
||||
struct SpellMgr{void*GetSpellInfo(unsigned){return nullptr;}}spells;auto sSpellMgr=&spells;
|
||||
unsigned FlightType(void*){return 1;}unsigned urand(unsigned low,unsigned){return low;}
|
||||
struct ClientlessFlightMgr{struct Record{bool Moving=false;};bool enabled=true;
|
||||
bool Enabled(Player*){return enabled;}bool PrepareFollowingMount(Player*);void HoldFollowing(Player*,Record&);
|
||||
};
|
||||
'''+method('bool ClientlessFlightMgr::PrepareFollowingMount(')+method('void ClientlessFlightMgr::HoldFollowing(')+r'''
|
||||
int main(){
|
||||
ClientlessFlightMgr m;Player p;
|
||||
assert(m.PrepareFollowingMount(&p)&&p.casts==1&&p.removals==1&&travel.cancels==1);
|
||||
p.mounted=p.fly=true;assert(!m.PrepareFollowingMount(&p)&&p.casts==1); // retain native flight mount
|
||||
p.fly=false;p.casting=true;assert(m.PrepareFollowingMount(&p)&&p.casts==1);p.casting=false;
|
||||
p.combat=true;assert(!m.PrepareFollowingMount(&p));p.combat=false;
|
||||
p.outdoors=false;assert(!m.PrepareFollowingMount(&p));p.outdoors=true;
|
||||
p.transport=true;assert(!m.PrepareFollowingMount(&p));p.transport=false;
|
||||
p.taxi=true;assert(!m.PrepareFollowingMount(&p));p.taxi=false;
|
||||
p.falling=true;assert(!m.PrepareFollowingMount(&p));p.falling=false;
|
||||
p.water=true;assert(!m.PrepareFollowingMount(&p));p.water=false;
|
||||
battle.allowed=false;assert(!m.PrepareFollowingMount(&p));battle.allowed=true;
|
||||
p.learned=false;assert(!m.PrepareFollowingMount(&p));p.learned=true;
|
||||
p.capability.Flags=0;assert(!m.PrepareFollowingMount(&p));p.capability.Flags=1;
|
||||
profile.FlightMounts[0].Weight=0;assert(!m.PrepareFollowingMount(&p));profile.FlightMounts[0].Weight=1;
|
||||
p.castResult=1;assert(!m.PrepareFollowingMount(&p));
|
||||
ClientlessFlightMgr::Record r;r.Moving=true;
|
||||
auto stops=p.stops;m.HoldFollowing(&p,r);
|
||||
assert(!r.Moving&&p.stops==stops+1&&p.flags==MOVEMENTFLAG_FLYING&&p.tier==unsigned(AnimTier::Fly)&&p.updates==1);
|
||||
m.HoldFollowing(&p,r);assert(p.updates==1&&p.stops==stops+1&&p.animations==1); // no idle packet flood
|
||||
p.tier=0;m.HoldFollowing(&p,r);assert(p.updates==2&&p.animations==2); // restore interrupted animation
|
||||
}
|
||||
'''
|
||||
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)
|
||||
print('Group flight mount: native admission, mount retention, casting wait, rejected capabilities, stationary flight animation and bounded publication passed')
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Exercise the real rescue executor with explicit graph and native transfer doubles."""
|
||||
from pathlib import Path
|
||||
import subprocess,tempfile
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
s=(root/'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text()
|
||||
a=s.index('bool ClientlessActionExecutorMgr::TryRescueTravel(');b=s.index('bool ClientlessActionExecutorMgr::RecoverTravel(',a)
|
||||
code=r'''
|
||||
#include <cassert>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
using uint32=unsigned;
|
||||
#define TC_LOG_WARN(...) ((void)0)
|
||||
struct Node{unsigned MapId=369;std::string Type="rescue";float X=33,Y=40,Z=-4,O=0;}node;
|
||||
struct Session{unsigned acks=0;void CompleteClientlessTeleport(){++acks;}}session;
|
||||
struct Player{
|
||||
unsigned map=369,transfers=0,stops=0;bool alive=true,combat=false,teleport=false,transport=false,taxi=false,flying=false,transferOK=true;
|
||||
bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return transport;}
|
||||
bool IsInFlight(){return taxi;}bool IsFlying(){return flying;}unsigned GetMapId(){return map;}
|
||||
void StopMoving(){++stops;}Session*GetSession(){return &session;}
|
||||
bool TeleportTo(unsigned m,float,float,float,float){assert(m==369);++transfers;return transferOK;}
|
||||
};
|
||||
struct ClientlessTravelRouteStep{std::string NodeKey;};
|
||||
struct Graph{bool available=true,transfer=false,forceSeen=false;unsigned calls=0;
|
||||
bool PlanRescueRoute(Player*,std::vector<ClientlessTravelRouteStep>&r,std::string&t,bool force,std::unordered_set<uint32>const*){
|
||||
++calls;forceSeen=force;if(!available)return false;r={{"rescue"},{"exit"}};t=(transfer||force)?"rescue":"";return true;}
|
||||
Node*FindNode(std::string const&){return &node;}
|
||||
}graph;auto sClientlessTravelGraphMgr=&graph;
|
||||
struct Areas{bool allowed=true;bool AllowsNode(Player*,Node const&){return allowed;}}areas;auto sAdventureAreaMgr=&areas;
|
||||
struct Core{bool managed=true;bool IsManaged(unsigned){return managed;}}core;auto sClientlessPlayerMgr=&core;
|
||||
struct Travel{unsigned cancels=0;void CancelTravel(unsigned){++cancels;}}travel;auto sClientlessTravelMgr=&travel;
|
||||
enum class ClientlessActionExecutionState{Moving,Failed};
|
||||
struct ClientlessActionExecutorMgr{
|
||||
struct Record{
|
||||
bool RescueActive=false,RescueWalkFailed=false,StopAtNode=true,Active=false;
|
||||
unsigned RescueAttempts=0;std::unordered_set<unsigned>FailedEdgeDatabaseIds;
|
||||
std::vector<ClientlessTravelRouteStep>Route;unsigned RouteStep=99;std::vector<int>EdgePath;
|
||||
bool DeferredStartActive=true,GroundExitActive=true,GroundResumeActive=true,TaxiActive=true,RecoveryWanderActive=true;
|
||||
bool TransportActive=true,FlightActive=true,FlightApproachActive=true,PortalUseActive=true,FormationWaiting=true,GroundChunk=true,ReplanAtGroundEnd=true,GroundFlightExit=true;
|
||||
bool FinalLocalLegActive=true,FinalLocalLegPending=true,RouteComplete=false;
|
||||
std::string originalGoal="original-intent";
|
||||
struct{ClientlessActionExecutionState State=ClientlessActionExecutionState::Failed;std::string Detail;}Status;
|
||||
};unsigned starts=0;bool StartRouteStep(Player*,unsigned,Record&){++starts;return true;}
|
||||
bool TryRescueTravel(Player*,unsigned,Record&);
|
||||
};
|
||||
'''+s[a:b]+r'''
|
||||
int main(){
|
||||
ClientlessActionExecutorMgr m;Player p;ClientlessActionExecutorMgr::Record r;
|
||||
assert(m.TryRescueTravel(&p,58,r)&&r.RescueActive&&r.RescueAttempts==1&&p.transfers==0);
|
||||
assert(r.Route.size()==2&&r.RouteStep==0&&!r.StopAtNode&&!r.FinalLocalLegPending&&!r.TransportActive&&!r.ReplanAtGroundEnd);
|
||||
assert(r.originalGoal=="original-intent");assert(!m.TryRescueTravel(&p,58,r));
|
||||
r.RescueWalkFailed=true;assert(m.TryRescueTravel(&p,58,r)&&graph.forceSeen&&p.transfers==1&&session.acks==1);
|
||||
assert(!m.TryRescueTravel(&p,58,r)); // bounded attempts
|
||||
r={};graph.transfer=true;p.map=0;assert(!m.TryRescueTravel(&p,58,r)&&p.transfers==1);p.map=369;
|
||||
r={};areas.allowed=false;assert(!m.TryRescueTravel(&p,58,r));areas.allowed=true;
|
||||
r={};node.Type="ground";assert(!m.TryRescueTravel(&p,58,r));node.Type="rescue";
|
||||
r={};p.combat=true;auto calls=graph.calls;assert(!m.TryRescueTravel(&p,58,r)&&graph.calls==calls);p.combat=false;
|
||||
p.transport=true;assert(!m.TryRescueTravel(&p,58,r));p.transport=false;
|
||||
p.flying=true;assert(!m.TryRescueTravel(&p,58,r));p.flying=false;
|
||||
p.alive=false;assert(!m.TryRescueTravel(&p,58,r));p.alive=true;
|
||||
core.managed=false;assert(!m.TryRescueTravel(&p,58,r));core.managed=true;
|
||||
graph.available=false;assert(!m.TryRescueTravel(&p,58,r));
|
||||
}
|
||||
'''
|
||||
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)
|
||||
print('Rescue executor: walk first, tram-only validated transfer, native ACK, original goal retention, state reset, access and bounded retry guards passed')
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Execute production adaptive planning and explicit recorded rescue search."""
|
||||
from pathlib import Path
|
||||
import runpy,subprocess,tempfile
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
e=runpy.run_path(str(root/'tests/clientless/test_free_flight_planner.py'))
|
||||
s=e['s']
|
||||
def method(sig):
|
||||
a=s.index(sig);b=s.index('{',a);n=1;c=b+1
|
||||
while n:n+=(s[c]=='{')-(s[c]=='}');c+=1
|
||||
return s[a:c]
|
||||
preamble=e['preamble'].replace('uint32 GetMapId()const{return 0;}','uint32 map=369; uint32 GetMapId()const{return map;} float GetExactDist(float a,float b,float c)const{return std::sqrt((x-a)*(x-a)+(y-b)*(y-b)+(z-c)*(z-c));}')
|
||||
cls=e['cls'].replace('\n};','''
|
||||
std::vector<ClientlessTravelEdgePoint>const*GetEdgePoints(uint32 id)const{auto i=_edgePoints.find(id);return i==_edgePoints.end()?nullptr:&i->second;}
|
||||
bool PlanRescueRoute(Player*,std::vector<ClientlessTravelRouteStep>&,std::string&,bool=false,std::unordered_set<uint32>const* =nullptr);
|
||||
bool FindNearestNodeToPosition(uint32,float,float,float,std::string&,float&)const;
|
||||
};''')
|
||||
code=preamble+e['structs']+cls+e['method']+method('bool ClientlessTravelGraphMgr::FindNearestNodeToPosition(')+method('bool ClientlessTravelGraphMgr::PlanRescueRoute(')+r'''
|
||||
void node(ClientlessTravelGraphMgr&g,std::string k,float x,std::string type="ground"){
|
||||
ClientlessTravelNode n;n.Key=k;n.Type=type;n.X=x;n.MapId=369;g._nodes[k]=n;
|
||||
}
|
||||
void edge(ClientlessTravelGraphMgr&g,std::string a,std::string b,unsigned id){
|
||||
g._edges[a].push_back({id,a,b,"ground",10,false});
|
||||
ClientlessTravelEdgePoint p;p.X=g._nodes.at(a).X;p.MapId=369;g._edgePoints[id].push_back(p);
|
||||
p.X=g._nodes.at(b).X;g._edgePoints[id].push_back(p);
|
||||
}
|
||||
int main(){
|
||||
ClientlessTravelGraphMgr g;Player p;p.x=5000;
|
||||
node(g,"sw",0,"rescue");node(g,"if",2500,"rescue");node(g,"exit_sw",10);node(g,"exit_if",2490);
|
||||
edge(g,"sw","exit_sw",1);edge(g,"if","exit_if",2);edge(g,"sw","if",3);
|
||||
std::vector<ClientlessTravelRouteStep>r;std::string transfer,error,reached;bool complete=false;
|
||||
// Rescue roots are excluded from ordinary nearest anchors and destinations.
|
||||
float distance;assert(g.FindNearestNodeToPosition(369,0,0,0,reached,distance)&&reached=="exit_sw");
|
||||
assert(!g.PlanAdaptiveRoute(&p,"sw",4000,200,r,complete,reached,error));
|
||||
// A recorded rescue corridor cannot be borrowed as a normal shortcut,
|
||||
// including virtual entries into the interior of its ground edges.
|
||||
p.x=0;g.PlanAdaptiveRoute(&p,"exit_if",1,200,r,complete,reached,error);assert(!complete);p.x=5000;
|
||||
// No traversable local start: only map369 may transfer to the nearest
|
||||
// permitted authored rescue node with a recorded escape edge.
|
||||
travel.complete=false;
|
||||
assert(g.PlanRescueRoute(&p,r,transfer)&&transfer=="if"&&r.size()==2&&r.back().EdgeDatabaseId==2);
|
||||
assert(!g._rescuePlanning); // scoped privilege cannot leak into later plans
|
||||
p.map=0;assert(!g.PlanRescueRoute(&p,r,transfer));p.map=369;
|
||||
// A reachable MMAP approach walks; it must not transfer a healthy bot.
|
||||
travel.complete=true;p.x=2497;
|
||||
assert(g.PlanRescueRoute(&p,r,transfer)&&transfer.empty()&&r.back().NodeKey=="exit_if");
|
||||
assert(g.PlanRescueRoute(&p,r,transfer,true)&&transfer=="if");
|
||||
// Blocked recording and faction/area denial select another legal escape;
|
||||
// no bypass of a denied rescue root or native edge is allowed.
|
||||
std::unordered_set<uint32>blocked{2};travel.complete=false;
|
||||
assert(g.PlanRescueRoute(&p,r,transfer,true,&blocked)&&transfer=="sw");
|
||||
access.blocked.insert("sw");assert(!g.PlanRescueRoute(&p,r,transfer,true,&blocked));access.blocked.clear();
|
||||
blocked={1,2,3};assert(!g.PlanRescueRoute(&p,r,transfer,true,&blocked));
|
||||
// A node without a recorded edge cannot become a teleport destination.
|
||||
g._edgePoints.clear();assert(!g.PlanRescueRoute(&p,r,transfer,true));
|
||||
}
|
||||
'''
|
||||
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('Rescue graph: normal exclusion, walking first, recorded-only escapes, bounded tram transfer, access/blocked-edge checks and scoped planning passed')
|
||||
Reference in new issue
Block a user