78 lines
6.1 KiB
Python
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.')
|