Finish capital waiting travel and adaptive flight entry up to 10,000 yards. Preserve checked vertical climbs, recover blocked ground departures and find validated dry landings. Retain group routes and Undercity arrival constraints. Restore defense from native combat references, coordinate instance rendezvous and browser reentry, finalize clientless sessions before map destruction and transfer Halaa capture-point ownership safely. Include the associated regression tests, investigation reports and reusable diagnostic scripts. Keep generated runtime evidence and server binaries local through scoped ignore rules. Validation/build/live evidence is recorded in the session checkpoint and individual reports.
93 lines
7.9 KiB
Python
93 lines
7.9 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 <ctime>
|
|
#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;
|
|
struct GroupTravel{bool hold=false;bool HoldForInstanceLeader(Player*){return hold;}}groupTravel;auto sAdventureGroupTravelMgr=&groupTravel;
|
|
'''+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);
|
|
// A pending human transfer or a temporarily bot-only group cannot eject
|
|
// members who already entered. The shared rendezvous gate owns waiting.
|
|
human.map=0;human.teleporting=true;groupTravel.hold=true;
|
|
assert(mgr.Update(&bot,500)&&bot.map==100&&bot.teleports==0);
|
|
human.playerSession.clientless=true;
|
|
assert(mgr.Update(&bot,500)&&bot.map==100&&bot.teleports==0);
|
|
human.playerSession.clientless=false;human.map=100;human.teleporting=false;groupTravel.hold=false;
|
|
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')
|