Improve AdventureBot planning and travel performance
This commit is contained in:
1 parent
69b5a1c4a1
commit
d1ef007cef
18 files changed
+906
-27
No files matched your search
@@ -55,15 +55,15 @@ namespace ObjectAccessor{std::unordered_map<ObjectGuid,Player*>players;Player*Fi
|
||||
namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}}
|
||||
struct Areas{void*LookupEntry(unsigned){return nullptr;}}sAreaTriggerStore;
|
||||
enum class ClientlessActionExecutionState{Moving,SelectingTarget,Completed,Failed};
|
||||
struct Executor{struct Status{ClientlessActionExecutionState State=ClientlessActionExecutionState::Completed;}status;unsigned clears=0;auto GetStatus(unsigned){return &status;}bool RequestStopAtNode(unsigned){return true;}void Clear(unsigned){++clears;status.State=ClientlessActionExecutionState::Completed;}void SetGroundPathAdapter(unsigned,std::function<bool(Player*,std::vector<Position>&)>){}}executor;auto sClientlessActionExecutorMgr=&executor;
|
||||
struct Executor{struct Status{ClientlessActionExecutionState State=ClientlessActionExecutionState::Completed;}status;unsigned clears=0,adapters=0;auto GetStatus(unsigned){return &status;}bool RequestStopAtNode(unsigned){return true;}void Clear(unsigned){++clears;status.State=ClientlessActionExecutionState::Completed;}void SetGroundPathAdapter(unsigned,std::function<bool(Player*,std::vector<Position>&)> adapter){adapters+=adapter?1:-1;}}executor;auto sClientlessActionExecutorMgr=&executor;
|
||||
struct ClientlessAction{std::string Name;std::unordered_map<std::string,std::string>Parameters;};
|
||||
struct Core{unsigned queued=0;std::vector<std::string>nodes;unsigned GetQueuedActionCount(unsigned){return queued;}bool QueueAction(unsigned,ClientlessAction action){++queued;nodes.push_back(action.Parameters["node"]);return true;}void ClearQueuedActions(unsigned){queued=0;}}core;auto sClientlessPlayerMgr=&core;
|
||||
enum class ClientlessTravelState{Moving,Arrived,Stuck,PathRejected};
|
||||
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float PathLength=20;};
|
||||
struct Travel{struct Status{ClientlessTravelState State=ClientlessTravelState::Arrived;Position Target;}status;bool valid=true;unsigned starts=0,cancels=0;std::vector<Position>targets;
|
||||
struct Travel{struct Status{ClientlessTravelState State=ClientlessTravelState::Arrived;Position Target;}status;bool valid=true;unsigned starts=0,cancels=0,validators=0;std::vector<Position>targets;
|
||||
auto GetStatus(unsigned){return &status;}bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics&d){d.Complete=valid;return valid;}
|
||||
bool StartTravel(Player*,Position const&target,bool=true,bool=false){++starts;targets.push_back(target);status.Target=target;status.State=ClientlessTravelState::Moving;return true;}
|
||||
std::vector<Position>const*GetActivePath(unsigned){return nullptr;}void CancelTravel(unsigned){++cancels;status.State=ClientlessTravelState::Arrived;}void SetPathValidator(unsigned,std::function<bool(Player*,std::vector<Position>const&)>){}
|
||||
std::vector<Position>const*GetActivePath(unsigned){return nullptr;}void CancelTravel(unsigned){++cancels;status.State=ClientlessTravelState::Arrived;}void SetPathValidator(unsigned,std::function<bool(Player*,std::vector<Position>const&)> validator){validators+=validator?1:-1;}
|
||||
}travel;auto sClientlessTravelMgr=&travel;
|
||||
struct PathGenerator{struct Point{float x,y,z;};Player*p;std::vector<Point>points;PathGenerator(Player*who):p(who){}bool CalculatePath(float x,float y,float z){points={{p->GetPositionX(),p->GetPositionY(),p->GetPositionZ()},{x,y,z}};return travel.valid;}auto const&GetPath(){return points;}};
|
||||
struct Graph{bool FindNearestNodeToPosition(unsigned,float,float,float,std::string&n,float&distance){n="nearest";distance=5;return true;}}graph;auto sClientlessTravelGraphMgr=&graph;
|
||||
@@ -150,6 +150,12 @@ int main(){
|
||||
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);
|
||||
// Leaving a group clears formation-owned adapters and validators before the
|
||||
// next autonomous route. This prevents valid MMAP paths being rejected by a
|
||||
// stale group formation safety check.
|
||||
assert(mgr._records.count(26)&&executor.adapters==2&&travel.validators==2);
|
||||
auto cancelsBefore=travel.cancels;a.g=nullptr;mgr.Observe(&a,500);
|
||||
assert(!mgr._records.count(26)&&executor.adapters==1&&travel.validators==1&&travel.cancels==cancelsBefore+1);
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as d:
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Exercise the production parser and plan editor, including persistence and preview."""
|
||||
from pathlib import Path
|
||||
import subprocess,tempfile
|
||||
root=Path(__file__).resolve().parents[2]
|
||||
source=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
|
||||
start=source.index('bool AdventureBehaviorMgr::EditPlan(')
|
||||
end=len(source)
|
||||
code=r'''
|
||||
#include "AdventurePlanCommand.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cassert>
|
||||
#include <ctime>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cstdarg>
|
||||
#include <cstdio>
|
||||
using uint32=unsigned;constexpr unsigned TEAM_ALLIANCE=0;
|
||||
namespace AdventureGroupPolicy{bool Eligible(std::string const&s){return s=="instance"||s=="raid"||s=="adventure";}}
|
||||
namespace AdventureBehaviorPolicy{struct Slot{std::string Intent,Location;};using Day=std::array<Slot,24>;}
|
||||
struct Player{unsigned guid,level=60,team=0;bool world=true;bool IsInWorld(){return world;}unsigned getLevel(){return level;}unsigned GetTeamId(){return team;}};
|
||||
struct Session{Player* player;Player*GetPlayer(){return player;}};
|
||||
struct World{std::map<unsigned,Session> sessions;Session*FindClientlessSession(unsigned g){auto i=sessions.find(g);return i==sessions.end()?nullptr:&i->second;}} world;auto sWorld=&world;
|
||||
struct Managed{bool IsManaged(unsigned){return true;}} managed;auto sClientlessPlayerMgr=&managed;
|
||||
struct Location{unsigned ZoneId;struct{std::string Id;}Point;};
|
||||
struct Locations{std::vector<Location> locations{{1519,{"sw"}},{1637,{"org"}},{1,{"dungeon"}},{1,{"raid"}},{1,{"forbidden"}}};
|
||||
bool EnsureLoaded(){return true;}auto const& Locations(){return locations;}Location const*Find(std::string const& key){for(auto&l:locations)if(l.Point.Id==key)return &l;return nullptr;}} locations;auto sAdventureLocationMgr=&locations;
|
||||
struct Tx{std::vector<std::string> sql;void PAppend(char const*fmt,...){char buf[1024];va_list ap;va_start(ap,fmt);vsnprintf(buf,sizeof(buf),fmt,ap);va_end(ap);sql.push_back(buf);}};
|
||||
struct Database{Tx tx;unsigned commits=0;Tx*BeginTransaction(){tx.sql.clear();return &tx;}void EscapeString(std::string&){}void DirectCommitTransaction(Tx*&){++commits;}}WorldDatabase;
|
||||
struct ChatHandler{std::vector<std::string> messages;void SendSysMessage(char const*s){messages.push_back(s);}void PSendSysMessage(char const*fmt,...){char buf[1024];va_list ap;va_start(ap,fmt);vsnprintf(buf,sizeof(buf),fmt,ap);va_end(ap);messages.push_back(buf);}};
|
||||
namespace GameTime{long GetGameTime(){return 100000;}}
|
||||
std::tm Local(long){std::tm tm{};tm.tm_year=126;tm.tm_mon=9;tm.tm_mday=31;tm.tm_hour=10;return tm;}
|
||||
unsigned DateOf(std::tm const&){return 20261031;}
|
||||
bool Known(std::string const&s){return s=="social"||s=="adventure"||s=="instance"||s=="raid"||s=="groupformation";}
|
||||
struct AdventureBehaviorMgr{
|
||||
struct DepartureState{long At=0;bool departed=false;bool Departed(long)const{return departed;}};
|
||||
struct Record{unsigned Date=20261031;bool Schedule=true,Enabled=true;int Hour=10;std::string ManualNextIntent,ManualNextLocation;AdventureBehaviorPolicy::Day Plan;DepartureState Departure;unsigned picks=0;};
|
||||
std::map<unsigned,Record>_records;unsigned switches=0,clears=0;
|
||||
bool EditPlan(ChatHandler*,std::string const&);
|
||||
bool Allowed(Player*p,Location const&l,std::string const&i){return l.Point.Id!="forbidden"&&(i!="raid"||p->level>=60)&&(l.Point.Id!="sw"||p->team==0)&&(l.Point.Id!="org"||p->team==1);}
|
||||
std::string PickLocation(Player*p,Record&r,std::string const&i){++r.picks;if(i=="raid"&&p->level<60)return "";return i=="instance"?"dungeon":i=="raid"?"raid":p->team?"org":"sw";}
|
||||
void ClearDeparture(Player*,Record&r){++clears;r.Departure={};}void SetWanted(Player*,Record&,std::string const&,std::string const&,long){++switches;}
|
||||
};
|
||||
'''.replace('struct Locations{','struct LocationCatalog{').replace('}} locations;', '}} locations;')
|
||||
# Class and member names must differ in C++.
|
||||
code+=source[start:end]
|
||||
code+=r'''
|
||||
int main(){
|
||||
using namespace AdventurePlanCommand;Request q;
|
||||
assert(Parse("all current social capital both 1 85 preview",10,q));assert(q.First==10&&q.Last==10&&q.Preview);
|
||||
assert(Parse("43 next+2 raid auto alliance 60 85 apply",10,q));assert(q.First==13&&q.Guid==43&&q.Faction==1);
|
||||
assert(Parse("all 12-15 keep auto horde 1 85 apply",10,q)&&q.Last==15);
|
||||
for(auto s:{"all next social auto both 0 85 apply","all next social auto both 85 1 apply","all next social auto both 1 86 apply","all 9 social auto both 1 85 apply","all next social auto both 1 85 typo","0 next social auto both 1 85 apply","all 14-12 social auto both 1 85 apply","all next social auto both 1 85 apply extra","4294967296 next social auto both 1 85 apply"})assert(!Parse(s,10,q));
|
||||
assert(!Parse("all next social auto both 1 85 apply",23,q));assert(!Parse("all next+23 social auto both 1 85 apply",10,q));
|
||||
assert(Parse("43 next raid auto alliance 60 85 apply",10,q));assert(Matches(q,43,60,1));assert(!Matches(q,44,60,1));assert(!Matches(q,43,59,1));assert(!Matches(q,43,60,2));
|
||||
Player a{43,60,0},b{44,30,1},c{45,60,0};world.sessions={{43,{&a}},{44,{&b}},{45,{&c}}};
|
||||
AdventureBehaviorMgr mgr;for(auto g:{43,44,45}){auto&r=mgr._records[g];for(auto&s:r.Plan)s={"social","sw"};}mgr._records[45].Date=20261030;
|
||||
ChatHandler h;
|
||||
assert(mgr.EditPlan(&h,"all next raid auto both 1 85 preview"));assert(WorldDatabase.commits==0&&mgr.switches==0);assert(mgr._records[43].picks==0&&mgr._records[43].Plan[11].Intent=="social");
|
||||
assert(mgr.EditPlan(&h,"all next raid auto both 1 85 apply"));assert(WorldDatabase.commits==1&&WorldDatabase.tx.sql.size()==1);assert(mgr._records[43].Plan[11].Intent=="raid");assert(mgr._records[44].Plan[11].Intent=="social");assert(mgr.switches==0);
|
||||
assert(mgr.EditPlan(&h,"44 next instance-or-raid auto horde 15 59 apply"));assert(mgr._records[44].Plan[11].Intent=="instance");
|
||||
assert(mgr.EditPlan(&h,"43 12-14 adventure capital alliance 60 60 apply"));assert(WorldDatabase.tx.sql.size()==3);assert(mgr._records[43].Plan[14].Location=="sw");assert(mgr._records[43].Plan[15].Intent=="social");
|
||||
auto commits=WorldDatabase.commits;assert(mgr.EditPlan(&h,"43 next raid forbidden both 1 85 apply"));assert(WorldDatabase.commits==commits);
|
||||
assert(mgr.EditPlan(&h,"43 22-23 groupformation capital both 1 85 apply"));assert(WorldDatabase.commits==commits&&mgr._records[43].Plan[22].Intent=="social");
|
||||
assert(mgr.EditPlan(&h,"43 current adventure capital both 1 85 apply"));assert(mgr.switches==1&&mgr.clears==1&&mgr._records[43].Hour==-1);
|
||||
assert(WorldDatabase.tx.sql[0].find("20261031,10,'adventure','sw'")!=std::string::npos);
|
||||
// Formation validates its next block, not just the meeting location.
|
||||
mgr._records[43].Plan[11]={"social","sw"};auto rejected=WorldDatabase.commits;
|
||||
assert(mgr.EditPlan(&h,"43 current groupformation capital both 1 85 apply"));assert(WorldDatabase.commits==rejected);
|
||||
mgr._records[43].Plan[11]={"raid","raid"};assert(mgr.EditPlan(&h,"43 current groupformation capital both 1 85 apply"));assert(mgr._records[43].Plan[10].Intent=="groupformation");
|
||||
mgr._records[43].Departure.At=1;mgr._records[43].Departure.departed=true;auto cleared=mgr.clears;
|
||||
assert(mgr.EditPlan(&h,"43 next adventure auto both 1 85 apply"));assert(mgr.clears==cleared);
|
||||
mgr._records[43].Departure.departed=false;assert(mgr.EditPlan(&h,"43 next adventure auto both 1 85 apply"));assert(mgr.clears==cleared+1);
|
||||
assert(!mgr.EditPlan(&h,"all current nonsense capital both 1 85 apply"));
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
p=Path(td);(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('Adventure plan commands: PASS')
|
||||
Reference in new issue
Block a user