Files
Viano-Core/tests/clientless/test_adventure_encounter.py
T
Nico a758239da9
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s
Implement AdventureBot class rotations, group roles and instance encounters
2026-10-28 22:37:18 +01:00

84 lines
7.3 KiB
Python

"""Run production mixed-group instance/wipe lifecycle with native-world adapters."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/game/World/AdventureEncounterMgr.cpp').read_text()
def function(sig):
a=s.index(sig);b=s.index('{',a);e=b+1;depth=1
while depth:depth+=(s[e]=='{')-(s[e]=='}');e+=1
return s[a:e]
header=(root/'src/server/game/World/AdventureEncounterMgr.h').read_text()
header='\n'.join(l for l in header.splitlines() if not l.startswith('#include')).replace('private:','public:')
code=r'''
#include <cassert>
#include <functional>
#include <unordered_map>
#include <vector>
#include <list>
#include <algorithm>
#include <utility>
#include <cmath>
using uint32=unsigned;using uint8=unsigned char;
#define TC_GAME_API
struct ObjectGuid{unsigned value=1;unsigned GetCounter(){return value;}};
struct Position{float x=0,y=0,z=0,o=0;void Relocate(float a,float b,float c,float d=0){x=a;y=b;z=c;o=d;}void Relocate(Position const*p){*this=*p;}float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;}float GetExactDist(Position const*p)const{return std::hypot(x-p->x,y-p->y);}};
struct Session{bool clientless=true;bool IsClientless(){return clientless;}};
struct Unit:Position{bool alive=true;unsigned map=100;bool IsAlive(){return alive;}unsigned GetMapId(){return map;}};
struct Player;
struct Ref{Player*p;Ref*nextRef=nullptr;Player*GetSource(){return p;}Ref*next(){return nextRef;}};
struct Group{Ref*first=nullptr;Ref*GetFirstMember(){return first;}}group;
struct Map{bool dungeon=true;bool IsDungeon(){return dungeon;}}inside,outside{false};
constexpr unsigned PLAYER_FLAGS=0,PLAYER_FLAGS_GHOST=1,CMSG_REPOP_REQUEST=2;
struct WorldPacket{WorldPacket(int,int){}template<class T>WorldPacket&operator<<(T){return *this;}};
struct Corpse:Position{unsigned GetMapId(){return 100;}unsigned GetInstanceId(){return 10;}};
struct Player:Unit{
ObjectGuid guid;Session session;Group*groupPtr=&group;bool combat=false,ghost=false,requested=false,teleporting=false;unsigned instance=10,teleports=0,revives=0;std::list<Unit*>companions;
struct PlayerSession:Session{Player*owner=nullptr;void HandleRepopRequestOpcode(WorldPacket&){owner->ghost=true;owner->map=0;owner->Relocate(-50,0,0);}}playerSession;
Player(){playerSession.owner=this;}ObjectGuid GetGUID(){return guid;}Group*GetGroup(){return groupPtr;}PlayerSession*GetSession(){return &playerSession;}
Corpse*GetCorpse(){return nullptr;}Map*GetMap(){return map==100?&inside:&outside;}unsigned GetInstanceId(){return instance;}
bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleporting;}bool IsInMap(Unit*u){return map==u->map;}
bool IsResurrectRequested(){return requested;}void ResurrectUsingRequestData(){alive=true;ghost=false;requested=false;++revives;}
bool HasFlag(unsigned,unsigned){return ghost;}void GetPartyMembers(std::list<Unit*>&v){v=companions;}
bool TeleportTo(unsigned m,float x,float y,float z,float o){map=m;Relocate(x,y,z,o);++teleports;return true;}
void ResurrectPlayer(float hp,bool sickness){assert(hp==.5f && !sickness);alive=true;ghost=false;++revives;}void SpawnCorpseBones(){}
};
struct Trigger{unsigned target_mapId;float target_X,target_Y,target_Z,target_Orientation;};
struct MapStore{Map*LookupEntry(unsigned){return &inside;}}sMapStore;
struct Objects{Trigger entrance{100,1,0,0,0},exit{0,10,0,0,0};Trigger const*GetMapEntranceTrigger(unsigned){return &entrance;}Trigger const*GetGoBackTrigger(unsigned){return &exit;}}objects;auto sObjectMgr=&objects;
enum class ClientlessTravelState{Moving,Arrived,Stuck,PathRejected};struct Status{ClientlessTravelState State=ClientlessTravelState::Arrived;Position Target;};
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;};
struct Travel{bool present=false,path=true,accept=true;unsigned starts=0;Status status;Status*GetStatus(unsigned){return present?&status:nullptr;}void CancelTravel(unsigned){present=false;}bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics&){return path;}bool StartTravel(Player*,Position const&p){++starts;if(accept){present=true;status.State=ClientlessTravelState::Moving;status.Target=p;}return accept;}}travel;auto sClientlessTravelMgr=&travel;
struct Behavior{unsigned actual=0;void EnterPlayerInstance(Player*){++actual;}}behavior;auto sAdventureBehaviorMgr=&behavior;
'''+header+r'''
AdventureEncounterMgr* AdventureEncounterMgr::instance(){static AdventureEncounterMgr m;return &m;}
bool AdventureEncounterMgr::AvoidGroundDamage(Player*){return false;}
bool AdventureEncounterMgr::FollowIntoInstance(Player*){return false;}
'''
for name in ('bool AdventureEncounterMgr::MixedGroup(', 'bool AdventureEncounterMgr::Active(', 'void AdventureEncounterMgr::Register(', 'void AdventureEncounterMgr::Offline(', 'bool AdventureEncounterMgr::Update(', 'bool AdventureEncounterMgr::Death('):code+=function(name)+'\n'
code+=r'''
int main(){
AdventureEncounterMgr mgr;Player bot,human,other;human.playerSession.clientless=false;human.guid.value=2;other.guid.value=3;
Ref a{&bot},b{&human},c{&other};a.nextRef=&b;b.nextRef=&c;group.first=&a;
assert(mgr.MixedGroup(&bot) && mgr.Active(&bot));human.playerSession.clientless=true;assert(!mgr.Active(&bot));assert(!mgr.Update(&bot,500));assert(mgr._records.empty());human.playerSession.clientless=false;
assert(!mgr.Update(&bot,500));assert(mgr._records.count(1));assert(behavior.actual==1);
unsigned hook=0;mgr.Register(100,[&](Player*,unsigned){++hook;return true;});assert(mgr.Update(&bot,500) && hook==1);mgr.Register(100,{});mgr._behaviors.clear();
bot.alive=false;bot.Relocate(35,0,0);bot.companions={&human};assert(mgr.Death(&bot));assert(!bot.ghost && bot.teleports==0); // waits while ally lives
human.alive=false;other.alive=false;bot.companions.clear();assert(mgr.Death(&bot));assert(bot.ghost && bot.map==0);
travel.path=false;assert(mgr.Death(&bot));assert(bot.map==0 && bot.x==10 && bot.teleports==1); // failed route permits entrance fallback
assert(mgr.Death(&bot));assert(bot.alive && !bot.ghost && bot.map==100 && bot.revives==1);
assert(mgr.Update(&bot,500));assert(travel.starts==0); // gather other dead bots first
other.alive=true;other.map=100;assert(mgr.Update(&bot,500));assert(travel.starts==1 && travel.status.Target.x==35); // returns to death point
human.alive=true;assert(!mgr.Update(&bot,500));assert(!mgr._records.at(1).Returning); // follows living player instead
bot.alive=false;bot.requested=true;assert(mgr.Death(&bot));assert(bot.alive && !bot.requested && bot.revives==2);
bot.groupPtr=nullptr;assert(mgr.Update(&bot,500));assert(bot.map==0 && bot.x==10); // kick exits safely
assert(!mgr.Update(&bot,500));assert(mgr._records.empty());
bot.groupPtr=&group;bot.map=100;bot.alive=false;bot.requested=false;assert(mgr.Death(&bot));assert(mgr._records.count(1)); // login already dead still establishes context
mgr.Offline(&bot);assert(mgr._records.empty());
}
'''
with tempfile.TemporaryDirectory() as d:
p=Path(d);(p/'test.cpp').write_text(code)
subprocess.run(['g++','-std=c++17',str(p/'test.cpp'),'-o',str(p/'test')],check=True)
subprocess.run([str(p/'test')],check=True)
print('Production encounters: bot-only exclusion, mixed mode, extension hook, wait/revive, wipe fallback/reentry/gather/return, living-player priority, kick exit and offline cleanup passed')