84 lines
7.5 KiB
Python
84 lines
7.5 KiB
Python
#!/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')
|