123 lines
8.6 KiB
Python
123 lines
8.6 KiB
Python
"""Execute production instance phases with native-transfer/persistence adapters."""
|
|
from pathlib import Path
|
|
import subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
s=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
|
|
start=s.index('bool AdventureBehaviorMgr::UpdateInstance(')
|
|
code=r'''
|
|
#include "AdventureInstancePolicy.h"
|
|
#include <cassert>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
#include <cmath>
|
|
using uint32=uint32_t;using uint8=uint8_t;
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
#define TC_LOG_WARN(...) ((void)0)
|
|
constexpr int UNIT_STAND_STATE_STAND=0,UNIT_STAND_STATE_SIT=1,EMOTE_ONESHOT_KNEEL=16,CMSG_AREATRIGGER=1;
|
|
struct Position{float x=0,y=0,z=0,o=0;Position()=default;Position(float a,float b,float c,float d=0):x(a),y(b),z(c),o(d){}float GetPositionX()const{return x;}float GetPositionY()const{return y;}float GetPositionZ()const{return z;}float GetOrientation()const{return o;}};
|
|
struct Trigger{uint32 ContinentID=0;struct{float X=0,Y=0,Z=0;}Pos;};
|
|
struct Target{uint32 target_mapId=36;float target_X=0,target_Y=0,target_Z=0,target_Orientation=0;};
|
|
struct Triggers{Trigger entrance,exit;Triggers(){exit.ContinentID=36;exit.Pos.X=100;}Trigger* LookupEntry(uint32 id){return id==78?&entrance:id==121?&exit:nullptr;}}sAreaTriggerStore;
|
|
struct Targets{Target entrance,exit;Targets(){exit.target_mapId=0;exit.target_X=500;}Target* GetAreaTrigger(uint32 id){return id==78?&entrance:id==121?&exit:nullptr;}}targets;auto sObjectMgr=&targets;
|
|
struct MapEntry{bool IsRaid(){return false;}}mapEntry;
|
|
struct Maps{auto LookupEntry(uint32){return &mapEntry;}}sMapStore;
|
|
struct WorldPacket{uint32 trigger=0;WorldPacket(int,int){}WorldPacket& operator<<(uint32 id){trigger=id;return *this;}};
|
|
struct Save{uint32 GetInstanceId(){return 7;}unsigned GetPlayerCount(){return 1;}unsigned GetGroupCount(){return 0;}}save;
|
|
struct Bind{Save* save=nullptr;};
|
|
struct Session;
|
|
struct Player:Position{
|
|
uint32 map=0,instance=0,health=100,maximum=100;int stand=0,damages=0,entryCalls=0,exitCalls=0,teleports=0;
|
|
bool safe=true,group=false,deny=false;Bind bind;Session* session=nullptr;
|
|
struct Guid{uint32 GetCounter(){return 26;}};Guid GetGUID(){return {};}
|
|
uint32 GetMapId(){return map;}uint32 GetInstanceId(){return instance;}bool IsInWorld(){return true;}
|
|
bool IsBeingTeleported(){return false;}bool GetGroup(){return group;}
|
|
Bind* GetBoundInstance(uint32,int){return bind.save?&bind:nullptr;}int GetDifficulty(bool){return 0;}
|
|
bool IsInAreaTriggerRadius(Trigger* t){return map==t->ContinentID&&std::fabs(x-t->Pos.X)<3;}
|
|
float GetAngle(float,float){return 0;}void SetFacingTo(float){}void HandleEmoteCommand(int){}
|
|
void SetStandState(int v){stand=v;}uint32 GetHealth(){return health;}uint32 GetMaxHealth(){return maximum;}
|
|
void SetHealth(uint32 h){health=h;++damages;}Session* GetSession(){return session;}
|
|
float GetExactDist(float a,float b,float c){return std::sqrt((x-a)*(x-a)+(y-b)*(y-b)+(z-c)*(z-c));}
|
|
float GetExactDist(Position const& p){return GetExactDist(p.x,p.y,p.z);}
|
|
bool TeleportTo(uint32 m,float a,float b,float c,float o){++teleports;map=m;instance=m==36?7:0;x=a;y=b;z=c;this->o=o;return true;}
|
|
};
|
|
struct Session{Player* p;void CompleteClientlessTeleport(){}void HandleAreaTriggerOpcode(WorldPacket const& packet){
|
|
if(packet.trigger==78){++p->entryCalls;if(!p->deny){p->map=36;p->instance=7;p->x=0;}}
|
|
else{++p->exitCalls;if(!p->deny){p->map=0;p->instance=0;p->x=500;}}
|
|
}};
|
|
bool Unsafe(Player* p){return !p->safe;}
|
|
struct Database{bool shared=false;template<class... T>bool PQuery(char const*,T...){return shared;}}CharacterDatabase;
|
|
struct Manager{void ClearQueuedActions(uint32){}}manager;auto sClientlessPlayerMgr=&manager;
|
|
struct Executor{void Clear(uint32){}}executor;auto sClientlessActionExecutorMgr=&executor;
|
|
struct Travel{void CancelTravel(uint32){}}travel;auto sClientlessTravelMgr=&travel;
|
|
struct Lua{void DismountGroundMount(Player*,char const*){}}lua;auto sClientlessLuaEngine=&lua;
|
|
struct AdventureBehaviorMgr{
|
|
enum class Phase{Idle,Travel,OnSite};
|
|
struct Record{AdventureInstancePolicy::Visit Instance;Phase State=Phase::OnSite;bool Dirty=false;std::string Detail;};
|
|
int saves=0,finishes=0;bool saveOK=true;
|
|
bool SaveInstance(Player*,Record&){++saves;return saveOK;}
|
|
void FinishInstance(Player* p,Record&r){++finishes;r.Instance={};p->SetStandState(0);}
|
|
bool UpdateInstance(Player*,Record&,time_t);
|
|
};
|
|
'''+s[start:]+r'''
|
|
using Stage=AdventureInstancePolicy::Phase;
|
|
AdventureBehaviorMgr::Record fresh(){AdventureBehaviorMgr::Record r;r.Instance.Active=true;r.Instance.Entry=78;r.Instance.Exit=121;r.Instance.Map=36;r.Instance.BlockEnd=3600;r.Instance.LossPercent=50;return r;}
|
|
int main(){
|
|
assert(AdventureInstancePolicy::ReturnAt(3600)==3300);
|
|
assert(AdventureInstancePolicy::MayEnter(3299,3600)&&!AdventureInstancePolicy::MayEnter(3300,3600));
|
|
assert(AdventureInstancePolicy::ReturnHealth(100,100,0)==100);
|
|
assert(AdventureInstancePolicy::ReturnHealth(100,100,50)==50);
|
|
assert(AdventureInstancePolicy::ReturnHealth(40,100,50)==40); // no healing
|
|
assert(AdventureInstancePolicy::ReturnHealth(UINT32_MAX,UINT32_MAX,50)==2147483647u);
|
|
AdventureBehaviorMgr mgr;auto r=fresh();Player p;Session session{&p};p.session=&session;
|
|
// 40 minutes' travel leaves 15 minutes inside and 5 outside.
|
|
assert(mgr.UpdateInstance(&p,r,2400)&&r.Instance.Stage==Stage::Preparing&&p.entryCalls==0);
|
|
assert(mgr.UpdateInstance(&p,r,2409)&&p.entryCalls==0);
|
|
assert(mgr.UpdateInstance(&p,r,2410)&&p.entryCalls==1&&p.map==36);
|
|
assert(mgr.UpdateInstance(&p,r,2411)&&r.Instance.Stage==Stage::Inside&&r.Instance.InstanceId==7);
|
|
int transfers=p.teleports;
|
|
assert(mgr.UpdateInstance(&p,r,3299)&&p.teleports==transfers&&p.exitCalls==0);
|
|
assert(mgr.UpdateInstance(&p,r,3300)&&r.Instance.Stage==Stage::Rest&&p.map==0&&p.x==500);
|
|
assert(p.exitCalls==1&&p.health==50&&p.damages==1&&r.Instance.InjuryApplied);
|
|
assert(mgr.UpdateInstance(&p,r,3400)&&p.damages==1);
|
|
assert(!mgr.UpdateInstance(&p,r,3600)&&mgr.finishes==1&&!r.Instance.Active);
|
|
// Persisted inside state on restart keeps the absolute return time.
|
|
r=fresh();r.Instance.Stage=Stage::Inside;r.Instance.InstanceId=7;p.map=36;p.instance=7;p.x=0;
|
|
assert(mgr.UpdateInstance(&p,r,3299)&&p.map==36);
|
|
assert(!mgr.UpdateInstance(&p,r,3700)&&p.map==0&&!r.Instance.Active);
|
|
// If native login ejected an expired copy, return to its associated exit.
|
|
r=fresh();r.Instance.Stage=Stage::Inside;r.Instance.InstanceId=7;p.map=0;p.x=999;
|
|
assert(mgr.UpdateInstance(&p,r,3400)&&p.x==500&&r.Instance.InjuryApplied);
|
|
// Replaying a saved return cannot compound damage.
|
|
r=fresh();r.Instance.Stage=Stage::Returning;r.Instance.InjuryApplied=true;p.x=500;
|
|
int damages=p.damages;assert(mgr.UpdateInstance(&p,r,3400)&&p.damages==damages);
|
|
// A failed durable entering save must not start a portal transfer.
|
|
r=fresh();r.Instance.Stage=Stage::Entering;p.map=0;p.x=0;mgr.saveOK=false;
|
|
int prevented=p.entryCalls;assert(mgr.UpdateInstance(&p,r,100)&&p.entryCalls==prevented);mgr.saveOK=true;
|
|
// A denied entrance is not a visit; no exit or damage is invented.
|
|
r=fresh();r.Instance.Stage=Stage::Entering;p.map=0;p.x=0;p.deny=true;damages=p.damages;
|
|
assert(mgr.UpdateInstance(&p,r,100)&&r.Instance.Stage==Stage::Rest&&p.damages==damages);
|
|
p.deny=false;
|
|
// Late arrival never enters, nor applies a fake injury.
|
|
r=fresh();r.State=AdventureBehaviorMgr::Phase::Travel;int calls=p.entryCalls;
|
|
assert(mgr.UpdateInstance(&p,r,3350)&&r.Instance.Stage==Stage::Rest&&p.entryCalls==calls&&p.damages==damages);
|
|
// Shared or grouped copies are not accepted for this solo performance.
|
|
r=fresh();r.Instance.Stage=Stage::Entering;p.group=true;
|
|
assert(mgr.UpdateInstance(&p,r,100)&&p.entryCalls==calls);p.group=false;
|
|
r=fresh();r.Instance.Stage=Stage::Entering;p.bind.save=&save;CharacterDatabase.shared=true;
|
|
assert(mgr.UpdateInstance(&p,r,100)&&p.entryCalls==calls);CharacterDatabase.shared=false;p.bind.save=nullptr;
|
|
// A denied exit remains pending and cannot wound a player still inside.
|
|
r=fresh();r.Instance.Stage=Stage::Returning;p.map=36;p.instance=7;p.x=0;p.deny=true;
|
|
assert(mgr.UpdateInstance(&p,r,3400)&&r.Instance.Stage==Stage::Returning&&p.damages==damages);
|
|
p.deny=false;
|
|
// With no separate native exit, use the original entrance location.
|
|
r=fresh();r.Instance.Stage=Stage::Returning;r.Instance.Exit=0;p.map=36;p.instance=7;p.x=50;
|
|
assert(mgr.UpdateInstance(&p,r,3400)&&p.map==0&&p.x==0);
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
src=Path(tmp)/'instance.cpp';exe=Path(tmp)/'instance';src.write_text(code)
|
|
subprocess.run(['g++','-std=c++17','-I'+str(root/'src/server/game/World'),str(src),'-o',str(exe)],check=True)
|
|
subprocess.run([str(exe)],check=True)
|
|
print('Production instance lifecycle: time budget, true entry, denied/shared entry, native rear exit, restart, expired copy, late arrival, HP and return replay passed.')
|