Manage Arthoran as a persistent level-60 tank profile, add Worgen forms and mounts, enable PvP and recruit through native LFG with preparation actions and player invitations. Reduce repeated route and flight-planning work, bound failed-travel retries and add runtime timing diagnostics. Include 86 passing tests and measurements with 110 online bots.
236 lines
18 KiB
Python
236 lines
18 KiB
Python
"""Execute leisure scene progression, native flight handover and pet-walk gating."""
|
|
from pathlib import Path
|
|
import subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
source=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
|
|
def function(signature):
|
|
a=source.index(signature);b=source.index('{',a);depth=1;e=b+1
|
|
while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1
|
|
return source[a:e]
|
|
code=r'''
|
|
#include <algorithm>
|
|
#include "AdventureInstancePolicy.h"
|
|
#include <cassert>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <ctime>
|
|
#include <random>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <unordered_set>
|
|
using uint32=uint32_t;
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
constexpr float INVALID_HEIGHT=-100000;
|
|
constexpr int BASE_ATTACK=0,ITEM_SUBCLASS_WEAPON_FISHING_POLE=20,UNIT_NPC_EMOTESTATE=0,EMOTE_STATE_FISHING=379;
|
|
struct Player;
|
|
struct Position{
|
|
float m_positionX=0,m_positionY=0,m_positionZ=0,o=0;
|
|
void Relocate(float x,float y,float z){m_positionX=x;m_positionY=y;m_positionZ=z;}
|
|
void Relocate(Player*);float GetPositionX()const{return m_positionX;}float GetPositionY()const{return m_positionY;}float GetPositionZ()const{return m_positionZ;}
|
|
float GetExactDist(Position const&p)const{return std::hypot(std::hypot(m_positionX-p.m_positionX,m_positionY-p.m_positionY),m_positionZ-p.m_positionZ);}
|
|
};
|
|
struct PositionFullTerrainStatus{bool outdoors=true;};
|
|
struct Terrain{
|
|
bool outdoors=true;uint32 zone=1519;
|
|
void GetFullTerrainStatusForPosition(int,uint32,float,float,float,PositionFullTerrainStatus& s){s.outdoors=outdoors;}
|
|
void GetZoneAndAreaId(int,uint32,uint32& z,uint32& a,Position const&){z=zone;a=1;}
|
|
}terrain;
|
|
struct Map{float water=-1000,ground=0;Terrain* GetTerrain(){return &terrain;}float GetHeight(int,float,float,float,bool,float){return ground;}float GetWaterLevel(int,float,float){return water;}}map;
|
|
struct Guid{uint32 GetCounter(){return 26;}};
|
|
struct Item{struct Template{uint32 GetSubClass(){return 20;}}t;Template* GetTemplate(){return &t;}}rod;
|
|
struct Creature:Position{}cat;
|
|
struct Player:Position{
|
|
bool alive=true,combat=false,flying=false,outdoors=true,water=false,falling=false,hasRod=false;uint32 emote=0;int stops=0;
|
|
uint32 GetMapId(){return 0;}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsFlying(){return flying;}bool IsInFlight(){return false;}bool GetTransport(){return false;}
|
|
bool IsOutdoors(){return outdoors;}bool IsInWater(){return water;}bool IsFalling(){return falling;}Guid GetGUID(){return {};}
|
|
int GetPhaseShift(){return 0;}Map* GetMap(){return ↦}Item* GetWeaponForAttack(int,bool){return hasRod?&rod:nullptr;}
|
|
void SetFacingTo(float f){o=f;}float GetOrientation(){return o;}void SetUInt32Value(int,uint32 v){emote=v;}uint32 GetUInt32Value(int){return emote;}
|
|
bool IsWithinLOS(float,float,float){return true;}
|
|
void StopMoving(){++stops;}float GetExactDist(Position const& p){return Position::GetExactDist(p);}float GetExactDist(Creature* p){return Position::GetExactDist(*p);}
|
|
};
|
|
void Position::Relocate(Player*p){*this=*p;}
|
|
bool Unsafe(Player* p){return p->combat||p->flying;}
|
|
constexpr int PATHFIND_NORMAL=1;
|
|
struct PathGenerator{
|
|
struct Point{float x,y,z;};std::vector<Point> path;
|
|
static inline bool reachable=true,broken=false;static inline int type=1;static inline float bend=20;
|
|
PathGenerator(Player*){}void SetPathLengthLimit(float){}
|
|
bool CalculatePath(Position const&a,Position const&b){
|
|
path.clear();int n=int(a.GetExactDist(b)/4)+1;
|
|
for(int i=0;i<=n;++i){float t=float(i)/n;path.push_back({a.m_positionX+(b.m_positionX-a.m_positionX)*t,a.m_positionY+(b.m_positionY-a.m_positionY)*t+bend*std::sin(3.14159265f*t),a.m_positionZ+(b.m_positionZ-a.m_positionZ)*t});}
|
|
if(broken && path.size()>5) path[5].z+=10;
|
|
return reachable;
|
|
}
|
|
int GetPathType(){return type;}auto const& GetPath(){return path;}
|
|
};
|
|
struct ClientlessCompanionScene{uint32 LeashSpell=0;bool Active=false,Ready=false,Leashed=false;};
|
|
struct ClientlessCompanionMgr{
|
|
static inline bool available=true,ready=true,valid=true;static inline int starts=0,resets=0;
|
|
static bool Available(Player*,uint32){return available;}
|
|
static bool Start(Player*,ClientlessCompanionScene& s,uint32,uint32){s.Active=true;s.Ready=false;++starts;return available;}
|
|
static bool Update(Player*,ClientlessCompanionScene& s,uint32){s.Ready=ready;return valid;}
|
|
static Creature* Resolve(Player*,ClientlessCompanionScene const&){return valid?&cat:nullptr;}
|
|
static void Reset(Player*,ClientlessCompanionScene& s){if(s.Active)++resets;s={};}
|
|
};
|
|
struct Profile{bool CanGroundMount=true;float GroundMountMinDistance=45;std::vector<int> GroundMounts{1},Companions;uint32 CompanionSpell=10673,CompanionLeashItem=37460;}profile;
|
|
struct Lua{uint32 SelectCompanionSpell(uint32){return profile.CompanionSpell;}int mounts=0,dismounts=0;Profile* GetAdventureProfile(uint32){return &profile;}bool PrepareGroundMount(Player*,float,char const*){++mounts;return true;}void DismountGroundMount(Player*,char const*){++dismounts;}}lua;auto sClientlessLuaEngine=&lua;
|
|
struct CombatMgr{bool available=true;int starts=0;bool TrainingAvailable(Player*){return available;}bool Train(Player*,uint32 duration,time_t){assert(duration>0);++starts;return available;}}combatMgr;auto sAdventureCombatMgr=&combatMgr;
|
|
struct AdventureMicroAction{std::string Requirement;};
|
|
struct Location{struct{std::string Category="leisure";float O=0;}Point;bool IsArea=false;uint32 AreaId=0,ZoneId=1519;}location;
|
|
struct Locations{Location* Find(std::string const&){return &location;}}locations;auto sAdventureLocationMgr=&locations;
|
|
enum class ClientlessTravelState{Moving,Arrived,PathRejected};
|
|
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float FinalDistance=0,PathLength=1;};
|
|
struct Travel{bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics&){return true;}struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;int cancels=0;void CancelTravel(uint32){++cancels;}auto GetStatus(uint32){return &status;}}travel;auto sClientlessTravelMgr=&travel;
|
|
enum class ClientlessFlightState{Mounting,Flying,Arrived,Failed};
|
|
struct Flight{struct{ClientlessFlightState State=ClientlessFlightState::Mounting;}status;int starts=0;bool enabled=true;Position target;bool Enabled(Player*){return enabled;}bool Start(Player*,Position const&t,std::string&){++starts;target=t;status.State=ClientlessFlightState::Mounting;return true;}auto GetStatus(uint32){return &status;}}flight;auto sClientlessFlightMgr=&flight;
|
|
struct Manager{void ClearQueuedActions(uint32){}}manager;auto sClientlessPlayerMgr=&manager;
|
|
struct Executor{bool RequestStopAtNode(uint32){return false;}}executor;auto sClientlessActionExecutorMgr=&executor;
|
|
class AdventureBehaviorMgr{public:
|
|
enum class Phase{Idle,OnSite,Switching};
|
|
struct Record{
|
|
AdventureInstancePolicy::Visit Instance;
|
|
std::string Intent="leisure",Location="city",WantedIntent,WantedLocation,Detail;Position Anchor;uint32 AnchorMap=0,MicroTimer=240000,RetryTimer=0;std::mt19937 Random{26};
|
|
time_t Deadline=0;bool Dirty=false;std::unordered_set<std::string> Tried;Phase State=Phase::OnSite;
|
|
struct LeisureScene{bool Active=false,Flying=false,Moving=false,StopRequested=false,Returning=false,PlannedWalk=false,Progressive=false;std::string Kind;Position Origin;std::vector<Position> Route,WalkTrace;std::size_t Step=0,OutboundSteps=0;uint32 Elapsed=0,LegElapsed=0,PauseMs=0,CompanionWait=0,CompletedLegs=0;ClientlessCompanionScene Companion;}Leisure;
|
|
};
|
|
int moves=0,resets=0;bool lastRiding=false,moveAllowed=true;Position requested;
|
|
bool LocalMove(Player*,Record&,Position const& target,float,bool riding=false){++moves;lastRiding=riding;requested=target;travel.status.State=ClientlessTravelState::Moving;return moveAllowed;}
|
|
void ResetMicro(Player* p,Record&r){++resets;ResetLeisure(p,r);}
|
|
void FinishInstance(Player*,Record&r){r.Instance={};}
|
|
bool workAvailable=false;bool ProfessionWorkAvailable(Player*,Record&){return workAvailable;}
|
|
bool FishingDirection(Player*,float&)const;
|
|
bool LeisureAvailable(Player*,Record const&,AdventureMicroAction const&)const;
|
|
bool PlanLeisureWalk(Player*,Record&);
|
|
bool StartLeisure(Player*,Record&,AdventureMicroAction const&);
|
|
bool UpdateLeisure(Player*,Record&,uint32);void ResetLeisure(Player*,Record&);
|
|
void SetWanted(Player*,Record&,std::string const&,std::string const&,time_t);
|
|
};
|
|
'''
|
|
for sig in ('bool AdventureBehaviorMgr::FishingDirection(', 'bool AdventureBehaviorMgr::PlanLeisureWalk(', 'bool AdventureBehaviorMgr::LeisureAvailable(', 'bool AdventureBehaviorMgr::StartLeisure(', 'bool AdventureBehaviorMgr::UpdateLeisure(', 'void AdventureBehaviorMgr::ResetLeisure(', 'void AdventureBehaviorMgr::SetWanted('):code+=function(sig)+'\n'
|
|
code+=r'''
|
|
int main(){
|
|
AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p;
|
|
assert(mgr.StartLeisure(&p,r,{"walk"})&&r.Leisure.PlannedWalk);
|
|
auto expected=r.Leisure.Route.size();auto outward=r.Leisure.OutboundSteps;
|
|
assert(outward>=3&&expected==2*outward);
|
|
assert(r.Leisure.Origin.GetExactDist(r.Leisure.Route[outward-1])>=65);
|
|
for(size_t i=0;i+1<outward;++i) assert(r.Leisure.Route[i].GetExactDist(r.Leisure.Route[2*outward-2-i])==0);
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.moves==1&&r.Leisure.Step==0&&!mgr.lastRiding);
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0); // moving is not arrival
|
|
bool active=true;for(int i=0;i<150&&active;++i){
|
|
if(r.Leisure.Moving){p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),mgr.requested.GetPositionZ());travel.status.State=ClientlessTravelState::Arrived;}
|
|
active=mgr.UpdateLeisure(&p,r,1000);
|
|
}
|
|
assert(!active&&r.Leisure.CompletedLegs==expected&&p.GetExactDist(r.Leisure.Origin)<0.01f);
|
|
mgr.ResetLeisure(&p,r);
|
|
// A rejected forward connector triggers the visited return path, never a skip.
|
|
assert(mgr.StartLeisure(&p,r,{"walk"}));mgr.moveAllowed=false;
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Returning&&r.Leisure.Step==0&&r.Leisure.CompletedLegs==0);
|
|
assert(!mgr.UpdateLeisure(&p,r,1000)&&r.Detail=="leisure return path unavailable");
|
|
mgr.moveAllowed=true;mgr.ResetLeisure(&p,r);
|
|
// Timer expiry retraces actually reached checkpoints, including bends.
|
|
assert(mgr.StartLeisure(&p,r,{"walk"}));
|
|
auto first=r.Leisure.Route[0],second=r.Leisure.Route[1];
|
|
assert(mgr.UpdateLeisure(&p,r,50));p.Relocate(first.m_positionX,first.m_positionY,first.m_positionZ);travel.status.State=ClientlessTravelState::Arrived;
|
|
assert(mgr.UpdateLeisure(&p,r,50)&&r.Leisure.PauseMs==0);
|
|
r.MicroTimer=0;p.Relocate(second.m_positionX,second.m_positionY,second.m_positionZ);travel.status.State=ClientlessTravelState::Arrived;
|
|
assert(mgr.UpdateLeisure(&p,r,50)&&r.Leisure.Returning&&r.Leisure.Route.size()==2);
|
|
assert(r.Leisure.Route[0].GetExactDist(first)==0&&r.Leisure.Route[1].GetExactDist(r.Leisure.Origin)==0);
|
|
mgr.ResetLeisure(&p,r);p.Relocate(0,0,0);r.MicroTimer=240000;
|
|
PathGenerator::reachable=false;assert(!mgr.StartLeisure(&p,r,{"walk"}));PathGenerator::reachable=true;
|
|
PathGenerator::type=2;assert(!mgr.StartLeisure(&p,r,{"walk"}));PathGenerator::type=1;
|
|
PathGenerator::broken=true;assert(!mgr.StartLeisure(&p,r,{"walk"}));PathGenerator::broken=false;
|
|
// Indoor seating must not trap the leisure selector. Foot outings can leave,
|
|
// and use the outdoor anchor rather than returning to the indoor chair.
|
|
p.outdoors=false;p.Relocate(5,0,2);
|
|
assert(mgr.LeisureAvailable(&p,r,{"walk"})&&mgr.LeisureAvailable(&p,r,{"roam"}));
|
|
assert(mgr.LeisureAvailable(&p,r,{"pet_walk"}));
|
|
assert(!mgr.LeisureAvailable(&p,r,{"ride"})&&!mgr.LeisureAvailable(&p,r,{"flight"}));
|
|
assert(mgr.StartLeisure(&p,r,{"walk"})&&r.Leisure.Origin.GetExactDist(r.Anchor)==0);
|
|
assert(r.Leisure.Route.back().GetExactDist(r.Anchor)==0);
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving&&!mgr.lastRiding);
|
|
mgr.ResetLeisure(&p,r);p.outdoors=true;p.Relocate(0,0,0);
|
|
// No route is invented where native terrain rejects every candidate or zone differs.
|
|
terrain.outdoors=false;assert(!mgr.StartLeisure(&p,r,{"walk"}));terrain.outdoors=true;
|
|
terrain.zone=12;assert(!mgr.StartLeisure(&p,r,{"walk"}));terrain.zone=1519;
|
|
map.ground=8;assert(!mgr.StartLeisure(&p,r,{"walk"}));map.ground=0; // roof is not a ground stroll waypoint
|
|
// Summon and leash must be ready before owner movement starts.
|
|
assert(mgr.StartLeisure(&p,r,{"pet_walk"}));ClientlessCompanionMgr::ready=false;
|
|
int before=mgr.moves;assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.moves==before);
|
|
ClientlessCompanionMgr::ready=true;cat.Relocate(p.GetPositionX(),p.GetPositionY(),0);
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.moves==before+1);
|
|
cat.Relocate(100,100,0);assert(mgr.UpdateLeisure(&p,r,1000)&&!r.Leisure.Moving&&p.stops>0);
|
|
bool following=true;for(int i=0;i<25&&following;++i)following=mgr.UpdateLeisure(&p,r,1000);
|
|
assert(!following);mgr.ResetLeisure(&p,r);assert(ClientlessCompanionMgr::resets==1);
|
|
cat.Relocate(0,0,0);p.Relocate(0,0,0);
|
|
// Leashed walking stops before native 5 yd range and waits for a real catch-up.
|
|
assert(mgr.StartLeisure(&p,r,{"pet_walk"}));r.Leisure.Companion.Leashed=true;
|
|
before=mgr.moves;cat.Relocate(4.1f,0,0);
|
|
assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before&&r.Leisure.CompanionWait==50);
|
|
cat.Relocate(3.7f,0,0);assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before);
|
|
cat.Relocate(3,0,0);assert(mgr.UpdateLeisure(&p,r,50)&&mgr.moves==before+1&&r.Leisure.CompanionWait==0);
|
|
mgr.ResetLeisure(&p,r);
|
|
// Mounted outings use the same navigation with an explicitly selected riding gait.
|
|
assert(mgr.StartLeisure(&p,r,{"ride"}));assert(mgr.UpdateLeisure(&p,r,1000)&&mgr.lastRiding&&lua.mounts>0);mgr.ResetLeisure(&p,r);
|
|
// A flight switch waits for native landing and a return to its origin.
|
|
assert(mgr.StartLeisure(&p,r,{"flight"}));assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Flying);
|
|
p.flying=true;int resets=mgr.resets;mgr.SetWanted(&p,r,"rest","inn",1234);
|
|
assert(r.Dirty&&r.Leisure.StopRequested&&mgr.resets==resets&&r.WantedIntent=="rest");
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0); // mounting/flying cannot fabricate arrival
|
|
p.flying=false;flight.status.State=ClientlessFlightState::Arrived;
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving);
|
|
p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),0);travel.status.State=ClientlessTravelState::Arrived;
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Returning&&r.Leisure.Route.size()==1&&r.Leisure.Flying);
|
|
flight.status.State=ClientlessFlightState::Arrived;assert(mgr.UpdateLeisure(&p,r,1000));
|
|
p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),0);travel.status.State=ClientlessTravelState::Arrived;
|
|
assert(!mgr.UpdateLeisure(&p,r,1000)&&p.GetExactDist(r.Leisure.Origin)<0.01f);mgr.ResetLeisure(&p,r);
|
|
// Angling needs an authored bank, equipped fishing rod and real water.
|
|
assert(!mgr.LeisureAvailable(&p,r,{"fish"}));location.Point.Category="leisure_fishing";p.hasRod=true;
|
|
assert(!mgr.StartLeisure(&p,r,{"fish"}));map.water=2;
|
|
assert(mgr.StartLeisure(&p,r,{"fish"})&&p.emote==EMOTE_STATE_FISHING);
|
|
r.MicroTimer=0;assert(!mgr.UpdateLeisure(&p,r,1000));mgr.ResetLeisure(&p,r);
|
|
// Adventure foot exploration reuses coherent native routes, never pet/flight scenes.
|
|
r.Intent="adventure";r.MicroTimer=180000;p.outdoors=true;p.Relocate(0,0,0);
|
|
assert(mgr.LeisureAvailable(&p,r,{"explore"})&&mgr.LeisureAvailable(&p,r,{"patrol"}));
|
|
assert(!mgr.LeisureAvailable(&p,r,{"pet_walk"})&&!mgr.LeisureAvailable(&p,r,{"flight"}));
|
|
assert(mgr.StartLeisure(&p,r,{"explore"})&&r.Leisure.PlannedWalk&&r.Leisure.Route.size()>=6);
|
|
mgr.ResetLeisure(&p,r);p.outdoors=false;assert(!mgr.LeisureAvailable(&p,r,{"explore"}));
|
|
|
|
// Profession area search progresses outward and stops only at confirmed arrival.
|
|
p.outdoors=true;p.Relocate(0,0,0);r.Intent="profession";r.MicroTimer=180000;
|
|
location.IsArea=true;location.AreaId=1519;
|
|
assert(mgr.StartLeisure(&p,r,{"work_search"})&&r.Leisure.Progressive);
|
|
assert(r.Leisure.Route.size()==r.Leisure.OutboundSteps);
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Moving);
|
|
mgr.workAvailable=true;
|
|
assert(mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==0);
|
|
p.Relocate(mgr.requested.GetPositionX(),mgr.requested.GetPositionY(),mgr.requested.GetPositionZ());
|
|
travel.status.State=ClientlessTravelState::Arrived;
|
|
assert(!mgr.UpdateLeisure(&p,r,1000)&&r.Leisure.Step==1&&r.Leisure.Route.size()==1);
|
|
mgr.ResetLeisure(&p,r);mgr.workAvailable=false;
|
|
// Profession fishing needs real shore water and a rod, without a fishing location key.
|
|
location.Point.Category="zone";map.water=-1000;p.hasRod=true;
|
|
assert(!mgr.LeisureAvailable(&p,r,{"fish"}));map.water=2;
|
|
assert(mgr.LeisureAvailable(&p,r,{"fish"}));p.hasRod=false;
|
|
assert(!mgr.LeisureAvailable(&p,r,{"fish"}));p.hasRod=true;p.water=true;
|
|
assert(!mgr.LeisureAvailable(&p,r,{"fish"}));p.water=false;
|
|
|
|
r.Intent="leisure";combatMgr.available=false;assert(!mgr.LeisureAvailable(&p,r,{"training"}));
|
|
combatMgr.available=true;assert(mgr.LeisureAvailable(&p,r,{"training"}));
|
|
assert(mgr.StartLeisure(&p,r,{"training"}) && combatMgr.starts==1);
|
|
r.Intent="adventure";assert(!mgr.LeisureAvailable(&p,r,{"training"}));
|
|
// Recruitment walks stay near the meeting point, also inside a tavern.
|
|
mgr.ResetLeisure(&p,r);r.Intent="groupformation";r.MicroTimer=45000;terrain.outdoors=false;p.outdoors=false;PathGenerator::bend=2;
|
|
assert(mgr.StartLeisure(&p,r,{"walk"}));
|
|
for(auto const& point:r.Leisure.Route)assert(point.GetExactDist(r.Leisure.Origin)<=18);
|
|
assert(!mgr.LeisureAvailable(&p,r,{"ride"}));mgr.ResetLeisure(&p,r);terrain.outdoors=true;PathGenerator::bend=20;
|
|
r.RetryTimer=30000;r.Tried.insert("old");mgr.SetWanted(&p,r,"rest","new",1234);assert(r.RetryTimer==0&&r.Tried.empty()&&r.Deadline==1234);
|
|
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-leisure-') as folder:
|
|
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
|
|
subprocess.run(['c++','-std=c++17','-I'+str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
|
|
subprocess.run([str(binary)],check=True)
|
|
print('Leisure: substantial curved MMAP routes, mirrored return, interrupted return, rejected corridors, actual arrival, companion follow, riding, flight and fishing passed.')
|