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

78 lines
6.1 KiB
Python

"""Execute production NeedsRole/Complete against actual party compositions."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/AdventureGroupMgr.cpp').read_text()
def function(signature):
a=source.index(signature);b=source.index('{',a);e=b+1;depth=1
while depth:
depth+=(source[e]=='{')-(source[e]=='}');e+=1
return source[a:e]
code=r'''
#include "AdventureGroupPolicy.h"
#include <cassert>
#include <string>
#include <vector>
#include <map>
#include <unordered_map>
using uint8=unsigned char;
constexpr unsigned CLASS_WARRIOR=1,CLASS_PALADIN=2,CLASS_DEATH_KNIGHT=6,CLASS_DRUID=11;
namespace lfg{constexpr uint8 PLAYER_ROLE_LEADER=1,PLAYER_ROLE_TANK=2,PLAYER_ROLE_HEALER=4,PLAYER_ROLE_DAMAGE=8;}
struct Guid{unsigned id=0;unsigned GetCounter()const{return id;}bool operator==(Guid other)const{return id==other.id;}bool operator<(Guid other)const{return id<other.id;}};
struct Player;struct Group;
struct Profile{std::string Role="damage";};
struct Session{bool bot=true;bool IsClientless(){return bot;}};
struct Player{Guid guid;Session session;Group*group=nullptr,*invite=nullptr;Profile profile;unsigned klass=1,spec=0;bool eligible=true,bear=false;
Guid GetGUID(){return guid;}Session*GetSession(){return &session;}Group*GetGroup(){return group;}Group*GetGroupInvite(){return invite;}unsigned getClass(){return klass;}bool HasAura(unsigned spell){return bear&&spell==5487;}};
struct Ref{Player*p;Ref*tail=nullptr;Player*GetSource(){return p;}Ref*next(){return tail;}};
struct Group{Guid leader;std::vector<Guid>members;Ref*first=nullptr;
struct Slot{Guid guid;};std::vector<Slot>GetMemberSlots(){std::vector<Slot>slots;for(auto g:members)slots.push_back({g});return slots;}
Guid GetLeaderGUID(){return leader;}unsigned GetMembersCount(){return members.size();}Ref*GetFirstMember(){return first;}uint8 GetLfgRoles(Guid){return 0;}};
namespace ObjectAccessor{std::unordered_map<unsigned,Player*>players;Player*FindPlayer(Guid g){auto i=players.find(g.id);return i==players.end()?nullptr:i->second;}}
struct Lua{Profile*GetAdventureProfile(unsigned id){auto p=ObjectAccessor::FindPlayer({id});return p&&p->session.bot?&p->profile:nullptr;}}lua;auto sClientlessLuaEngine=&lua;
struct PlayerAI{static bool IsPlayerHealer(Player*){return false;}static unsigned GetPlayerSpec(Player*p){return p->spec;}};
struct AdventureGroupPlan{bool Forming=true;std::string Intent="groupformation",NextIntent="instance";unsigned Capacity=5;};
struct Behavior{AdventureGroupPlan plan;AdventureGroupPlan GroupPlan(Player*){return plan;}}behavior;auto sAdventureBehaviorMgr=&behavior;
namespace GameTime{long now=100;long GetGameTime(){return now;}}
struct AdventureGroupMgr{struct Record{Guid Inviter;long InviteUntil=0;};std::map<Guid,Record>_records;unsigned PendingInvites(Player*,Player*except=nullptr)const;bool SurplusRole(Player*);bool BotLed(Player*)const{return true;}bool NeedsRole(Player*,Player*);bool Complete(Player*);bool CanInvite(Player*,Player*p){return p->eligible;}};
'''
for signature in ['std::string Activity(', 'unsigned Capacity(', 'uint8 BrowserRole(', 'unsigned AdventureGroupMgr::PendingInvites(', 'bool AdventureGroupMgr::SurplusRole(', 'bool AdventureGroupMgr::NeedsRole(', 'bool AdventureGroupMgr::Complete(']:code+='\n'+function(signature)
code+=r'''
int main(){
AdventureGroupMgr manager;Group group;std::vector<Player>players(7);std::vector<Ref>refs;
refs.reserve(5);
for(unsigned i=0;i<players.size();++i){players[i].guid={i+1};ObjectAccessor::players[i+1]=&players[i];if(i<5){players[i].group=&group;group.members.push_back(players[i].guid);refs.push_back({&players[i]});}}
for(unsigned i=0;i<4;++i)refs[i].tail=&refs[i+1];group.first=&refs[0];group.leader=players[0].guid;
players[0].profile.Role="tank";players[1].profile.Role="healer";
assert(manager.Complete(&players[0]));assert(!manager.SurplusRole(&players[0]));
players[2].profile.Role="tank";assert(manager.SurplusRole(&players[0]));assert(!manager.SurplusRole(&players[1]));players[2].profile.Role="damage";
players[6].session.bot=false;players[6].klass=CLASS_DRUID;players[6].bear=true;
assert(BrowserRole(&players[6])==lfg::PLAYER_ROLE_TANK);players[6].bear=false;
assert(BrowserRole(&players[6])==lfg::PLAYER_ROLE_DAMAGE);players[6].session.bot=true;
players[1].profile.Role="damage";assert(!manager.Complete(&players[0]));
// An already overfilled role is not a legal destination, even with another role missing.
players[5].profile.Role="healer";assert(!manager.NeedsRole(&players[0],&players[5]));
group.members.pop_back();refs[3].tail=nullptr;
assert(manager.NeedsRole(&players[0],&players[5]));
players[6].profile.Role="healer";players[6].invite=&group;
manager._records[players[6].guid]={players[0].guid,200};
assert(manager.PendingInvites(&players[0])==1);
assert(!manager.NeedsRole(&players[0],&players[5]));
assert(manager.NeedsRole(&players[0],&players[6])); // own reserved role remains acceptable
GameTime::now=201;assert(manager.PendingInvites(&players[0])==0);
assert(manager.NeedsRole(&players[0],&players[5]));manager._records.clear();players[6].invite=nullptr;
players[5].profile.Role="tank";assert(!manager.NeedsRole(&players[0],&players[5]));
players[1].profile.Role="healer";group.members.push_back(players[4].guid);refs[3].tail=&refs[4];
assert(manager.Complete(&players[0]));players[2].eligible=false;assert(!manager.Complete(&players[0]));players[2].eligible=true;
ObjectAccessor::players.erase(4);assert(!manager.Complete(&players[0]));ObjectAccessor::players[4]=&players[3];
group.members.pop_back();assert(!manager.Complete(&players[0]));
behavior.plan.NextIntent="pvp";assert(manager.Complete(&players[0]));
players[0].group=nullptr;assert(!manager.Complete(&players[0]));
}
'''
with tempfile.TemporaryDirectory() as directory:
p=Path(directory);(p/'roles.cpp').write_text(code)
subprocess.run(['g++','-std=c++17','-I'+str(root/'src/server/game/World'),str(p/'roles.cpp'),'-o',str(p/'roles')],check=True)
subprocess.run([str(p/'roles')],check=True)
print('Production role composition: correct party, missing healer, surplus tank, inaccessible/offline member and PvP group passed.')