229 lines
13 KiB
Python
229 lines
13 KiB
Python
"""Compile the production object-portal start/update methods against Core doubles."""
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
|
|
ROOT = Path(__file__).resolve().parents[2]
|
|
source = (ROOT / 'src/server/game/World/ClientlessActionExecutorMgr.cpp').read_text()
|
|
def production_method(signature):
|
|
start = source.index(signature)
|
|
opening = source.index('{', start)
|
|
depth, end = 1, opening + 1
|
|
while depth:
|
|
depth += (source[end] == '{') - (source[end] == '}')
|
|
end += 1
|
|
return source[start:end]
|
|
|
|
methods = '\n'.join(production_method(signature) for signature in (
|
|
'bool ClientlessActionExecutorMgr::StartPortalUse(',
|
|
'void ClientlessActionExecutorMgr::UpdatePortalUse(',
|
|
))
|
|
harness = r'''
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
using uint32=uint32_t; using uint64=uint64_t;
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
#define TC_LOG_WARN(...) ((void)0)
|
|
constexpr int GAMEOBJECT_FLAGS=0, GO_FLAG_NOT_SELECTABLE=16, GO_FLAG_LOCKED=2;
|
|
constexpr int GAMEOBJECT_TYPE_SPELLCASTER=22, CMSG_GAMEOBJ_USE=1;
|
|
struct WorldPacket { int guid=0; WorldPacket(int,int){} void operator<<(int v){guid=v;} };
|
|
struct GameObject {
|
|
int guid=83,entry=183325,flags=0,type=22;
|
|
bool spawned=true;
|
|
struct {struct {int spell=17334;} spellcaster;} info;
|
|
int GetEntry(){return entry;} int GetGUID(){return guid;}
|
|
bool isSpawned(){return spawned;} bool HasFlag(int,int f){return flags&f;}
|
|
int GetGoType(){return type;} auto GetGOInfo(){return &info;}
|
|
};
|
|
struct Map {
|
|
std::multimap<uint32,GameObject*> objects;
|
|
auto const& GetGameObjectBySpawnIdStore(){return objects;}
|
|
};
|
|
struct Player;
|
|
struct Session {
|
|
Player* player;
|
|
bool clientless=true,accept=true,ackWorks=true,far=true,wrongTarget=false;
|
|
int clicks=0,acks=0; uint64 sequence=0;
|
|
Player* GetPlayer(){return player;}
|
|
bool IsClientless(){return clientless;}
|
|
uint64 GetClientlessTeleportSequence(){return sequence;}
|
|
void HandleGameObjectUseOpcode(WorldPacket&);
|
|
void CompleteClientlessTeleport();
|
|
};
|
|
using WorldSession=Session;
|
|
struct Player {
|
|
int mapId=530;float x=-1894,y=5387,z=-12;
|
|
bool alive=true,pending=false,world=true,phase=true,visible=true,inRange=true,aboard=false;
|
|
Map map;Session session{this};GameObject go;
|
|
Player(){map.objects.emplace(83,&go);}
|
|
int GetMapId(){return mapId;}bool IsAlive(){return alive;}
|
|
bool IsBeingTeleported(){return pending;}bool IsInWorld(){return world;}
|
|
void* GetTransport(){return aboard?this:nullptr;}
|
|
Session* GetSession(){return &session;}Map* GetMap(){return ↦}
|
|
bool IsInPhase(GameObject*){return phase;}bool CanSeeOrDetect(GameObject*){return visible;}
|
|
GameObject* GetGameObjectIfCanInteractWith(int guid){return inRange&&guid==go.guid?&go:nullptr;}
|
|
float GetPositionX(){return x;}float GetPositionY(){return y;}float GetPositionZ(){return z;}
|
|
std::string GetName(){return "Arthoran";}void StopMoving(){}
|
|
};
|
|
void Session::HandleGameObjectUseOpcode(WorldPacket& packet) {
|
|
++clicks;assert(packet.guid==83);
|
|
if(accept){player->pending=true;if(far)player->world=false;}
|
|
}
|
|
void Session::CompleteClientlessTeleport(){
|
|
++acks;if(!ackWorks||!player->pending)return;
|
|
player->pending=false;player->world=true;++sequence;
|
|
player->mapId=wrongTarget?1:0;player->x=-8998;player->y=861;player->z=29;
|
|
}
|
|
struct Node {std::string Key;uint32 MapId=530;};
|
|
struct Portal {uint32 SpawnId=83,Entry=183325,SpellId=17334;Node Source;};
|
|
struct Graph {bool available=true;Portal portal;auto GetPortalUse(uint32){return available?&portal:nullptr;}} graph;
|
|
struct Travel {void CancelTravel(uint32){}} travel;
|
|
struct Lua {void DismountGroundMount(Player*,const char*){}} lua;
|
|
auto sClientlessTravelGraphMgr=&graph;auto sClientlessTravelMgr=&travel;auto sClientlessLuaEngine=&lua;
|
|
enum class ClientlessActionExecutionState {Moving,Failed,Completed};
|
|
struct Step {uint32 EdgeDatabaseId=9,MapId=0;std::string NodeKey="portal_go_83_target";bool EdgeReverse=false;float X=-8998,Y=861,Z=29;};
|
|
struct Record {
|
|
std::vector<Step> Route{{},{}};size_t RouteStep=1;
|
|
bool PortalUseActive=false,Active=true;uint64 PortalUseTeleportSequence=0;
|
|
uint32 PortalUseElapsedMs=0,PortalUseSpawnId=0,RecoveryCount=0;
|
|
std::vector<int> EdgePath;
|
|
struct {ClientlessActionExecutionState State=ClientlessActionExecutionState::Moving;std::string TargetName,Detail;} Status;
|
|
};
|
|
struct ClientlessActionExecutorMgr {
|
|
int continued=0;
|
|
bool StartPortalUse(Player*,uint32,Record&);
|
|
void UpdatePortalUse(Player*,uint32,Record&);
|
|
bool StartRouteStep(Player*,uint32,Record& r){++continued;r.Active=false;r.Status.State=ClientlessActionExecutionState::Completed;return true;}
|
|
};
|
|
'''
|
|
harness += methods
|
|
harness += r'''
|
|
int main(){
|
|
ClientlessActionExecutorMgr mgr;
|
|
{Player p;Record r;assert(mgr.StartPortalUse(&p,26,r));assert(p.session.clicks==1&&!p.world);
|
|
mgr.UpdatePortalUse(&p,26,r);assert(!r.PortalUseActive&&r.RouteStep==2&&p.mapId==0&&p.session.sequence==1);}
|
|
// The global lifecycle may have ACKed a delayed cast before our next update.
|
|
{Player p;Record r;assert(mgr.StartPortalUse(&p,26,r));p.session.CompleteClientlessTeleport();
|
|
mgr.UpdatePortalUse(&p,26,r);assert(r.RouteStep==2&&p.session.acks==1);}
|
|
{Player p;p.session.far=false;Record r;assert(mgr.StartPortalUse(&p,26,r));
|
|
mgr.UpdatePortalUse(&p,26,r);assert(r.RouteStep==2);}
|
|
// Denied native click/cast, even when already standing at the expected target.
|
|
{Player p;p.session.accept=false;p.mapId=530;p.x=-8998;p.y=861;p.z=29;
|
|
Record r;r.Route[1].MapId=530;assert(mgr.StartPortalUse(&p,26,r));
|
|
mgr.UpdatePortalUse(&p,26,r);assert(r.RouteStep==1&&r.Active);
|
|
r.PortalUseElapsedMs=10000;mgr.UpdatePortalUse(&p,26,r);assert(!r.Active&&r.RouteStep==1);}
|
|
{Player p;p.session.ackWorks=false;Record r;assert(mgr.StartPortalUse(&p,26,r));
|
|
r.PortalUseElapsedMs=10000;mgr.UpdatePortalUse(&p,26,r);assert(!r.Active&&p.pending&&r.RouteStep==1);}
|
|
{Player p;p.session.wrongTarget=true;Record r;assert(mgr.StartPortalUse(&p,26,r));
|
|
r.PortalUseElapsedMs=10000;mgr.UpdatePortalUse(&p,26,r);assert(!r.Active&&r.RouteStep==1);}
|
|
{Player p;Record r;assert(mgr.StartPortalUse(&p,26,r));p.session.CompleteClientlessTeleport();p.x+=6;
|
|
r.PortalUseElapsedMs=10000;mgr.UpdatePortalUse(&p,26,r);assert(!r.Active&&r.RouteStep==1);}
|
|
for(int kind=0;kind<12;++kind){
|
|
Player p;Record r;graph.available=true;
|
|
switch(kind){case 0:graph.available=false;break;case 1:r.RouteStep=0;break;
|
|
case 2:r.Route[1].EdgeReverse=true;break;case 3:p.mapId=1;break;
|
|
case 4:p.alive=false;break;case 5:p.pending=true;break;case 6:p.aboard=true;break;
|
|
case 7:p.session.clientless=false;break;case 8:p.map.objects.clear();break;
|
|
case 9:p.phase=false;break;case 10:p.inRange=false;break;case 11:p.go.flags=GO_FLAG_NOT_SELECTABLE;break;}
|
|
assert(!mgr.StartPortalUse(&p,26,r));assert(p.session.clicks==0&&!r.Active);
|
|
}
|
|
graph.available=true;
|
|
{Player p;p.go.info.spellcaster.spell=1;Record r;assert(!mgr.StartPortalUse(&p,26,r));assert(!p.session.clicks);}
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-portal-use-test-') as folder:
|
|
cpp, binary = Path(folder)/'test.cpp', Path(folder)/'test'
|
|
cpp.write_text(harness)
|
|
subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True)
|
|
subprocess.run([str(binary)], check=True)
|
|
print('Interactive portal native click, near/far/global ACK, denial, timeout and target checks passed.')
|
|
|
|
# Exercise native destination discovery rather than duplicating the importer.
|
|
graph_source = (ROOT / 'src/server/game/World/ClientlessTravelGraphMgr.cpp').read_text()
|
|
a = graph_source.index('bool ClientlessTravelGraphMgr::ResolvePortalUse(')
|
|
b = graph_source.index('ClientlessPortalUse const* ClientlessTravelGraphMgr::GetPortalUse', a)
|
|
definition_method = graph_source[a:b]
|
|
definition_harness = r'''
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <cstdint>
|
|
using uint8=uint8_t;using uint32=uint32_t;
|
|
constexpr uint32 MAX_SPELL_EFFECTS=3,GAMEOBJECT_TYPE_SPELLCASTER=22,SPELL_EFFECT_TELEPORT_UNITS=5,TARGET_DEST_DB=17;
|
|
using SpellEffIndex=uint8;
|
|
struct Position {float GetPositionX()const{return -1894;}float GetPositionY()const{return 5387;}float GetPositionZ()const{return -12;}float GetOrientation()const{return 0;}};
|
|
struct Spawn {uint32 id=183325,mapId=530;Position spawnPoint;} spawn;
|
|
struct Info {uint32 type=22;struct{uint32 spell=17334;}spellcaster;} info;
|
|
struct ObjectMgr {bool hasSpawn=true;auto GetGameObjectData(uint32){return hasSpawn?&spawn:nullptr;}auto GetGameObjectTemplate(uint32){return &info;}} objects;
|
|
struct Target{uint32 id=0;uint32 GetTarget()const{return id;}};
|
|
struct Effect{uint32 Effect=0;Target TargetA,TargetB;};
|
|
struct SpellInfo{uint32 Id=17334;Effect Effects[3];} spell;
|
|
struct SpellTargetPosition {uint32 target_mapId=0;float target_X=-8998,target_Y=861,target_Z=29,target_Orientation=2;} destination;
|
|
struct SpellMgr {bool hasSpell=true,hasTarget=true;auto GetSpellInfo(uint32){return hasSpell?&spell:nullptr;}auto GetSpellTargetPosition(uint32,SpellEffIndex){return hasTarget?&destination:nullptr;}} spells;
|
|
auto sObjectMgr=&objects;auto sSpellMgr=&spells;
|
|
struct Node {std::string Key,Type,WorldPointKey;uint32 MapId=0;float X=0,Y=0,Z=0,O=0;};
|
|
struct ClientlessPortalUse {uint32 SpawnId=0,Entry=0,SpellId=0;Node Source,Target;};
|
|
struct ClientlessTravelGraphMgr{static bool ResolvePortalUse(uint32,ClientlessPortalUse&,std::string&);};
|
|
'''
|
|
definition_harness += definition_method
|
|
definition_harness += r'''
|
|
int main(){
|
|
spell.Effects[0].Effect=5;spell.Effects[0].TargetB.id=17;
|
|
ClientlessPortalUse p;std::string error;
|
|
assert(ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));
|
|
assert(p.SpawnId==83&&p.Entry==183325&&p.SpellId==17334&&p.Source.MapId==530&&p.Target.MapId==0);
|
|
assert(p.Source.Key=="portal_go_83_source"&&p.Target.Key=="portal_go_83_target"&&p.Target.X==-8998);
|
|
objects.hasSpawn=false;assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));objects.hasSpawn=true;
|
|
info.type=10;assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));info.type=22;
|
|
spells.hasSpell=false;assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));spells.hasSpell=true;
|
|
spells.hasTarget=false;assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));spells.hasTarget=true;
|
|
spell.Effects[0].TargetB.id=0;assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));spell.Effects[0].TargetB.id=17;
|
|
spell.Effects[1].Effect=5;spell.Effects[1].TargetB.id=17;
|
|
assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));spell.Effects[1]={};
|
|
spell.Effects[0].Effect=77;assert(!ClientlessTravelGraphMgr::ResolvePortalUse(83,p,error));
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-portal-definition-test-') as folder:
|
|
cpp, binary = Path(folder)/'test.cpp', Path(folder)/'test'
|
|
cpp.write_text(definition_harness)
|
|
subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True)
|
|
subprocess.run([str(binary)], check=True)
|
|
print('Native portal destination discovery and unsupported/ambiguous spell rejection passed.')
|
|
|
|
movement_source = (ROOT / 'src/server/game/Handlers/MovementHandler.cpp').read_text()
|
|
a = movement_source.index('void WorldSession::CompleteClientlessTeleport()')
|
|
b = movement_source.index('void WorldSession::CompleteNearTeleport(', a)
|
|
ack_harness = r'''
|
|
#include <cassert>
|
|
#include <cstdint>
|
|
struct Player {bool pending=false,far=false,world=true;bool IsBeingTeleported(){return pending;}bool IsBeingTeleportedFar(){return pending&&far;}bool IsBeingTeleportedNear(){return pending&&!far;}bool IsInWorld(){return world;}};
|
|
struct WorldSession {
|
|
Player* player;bool clientless=true,works=true;int near=0,far=0;uint64_t _clientlessTeleportSequence=0;
|
|
bool IsClientless(){return clientless;}Player* GetPlayer(){return player;}
|
|
void HandleMoveWorldportAck(){++far;if(works){player->pending=false;player->world=true;}}
|
|
void CompleteNearTeleport(Player* p){++near;if(works)p->pending=false;}
|
|
void CompleteClientlessTeleport();
|
|
};
|
|
'''
|
|
ack_harness += movement_source[a:b]
|
|
ack_harness += r'''
|
|
int main(){
|
|
Player p;WorldSession s{&p};s.CompleteClientlessTeleport();assert(s._clientlessTeleportSequence==0);
|
|
p.pending=true;s.CompleteClientlessTeleport();assert(s.near==1&&s._clientlessTeleportSequence==1);
|
|
s.CompleteClientlessTeleport();assert(s._clientlessTeleportSequence==1);
|
|
p.pending=p.far=true;p.world=false;s.CompleteClientlessTeleport();assert(s.far==1&&s._clientlessTeleportSequence==2);
|
|
p.pending=true;s.works=false;s.CompleteClientlessTeleport();assert(s._clientlessTeleportSequence==2&&p.pending);
|
|
s.clientless=false;s.works=true;s.CompleteClientlessTeleport();assert(p.pending&&s._clientlessTeleportSequence==2);
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-portal-ack-test-') as folder:
|
|
cpp, binary = Path(folder)/'test.cpp', Path(folder)/'test'
|
|
cpp.write_text(ack_harness)
|
|
subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True)
|
|
subprocess.run([str(binary)], check=True)
|
|
print('Production clientless ACK completion sequence increments only after completed near/far transfers.')
|