Files
Viano-Core/tests/clientless/test_adventure_plan_commands.py
T

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')