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.
82 lines
5.6 KiB
Python
82 lines
5.6 KiB
Python
"""Execute the production loader selection, quotas, retry and rotation gates."""
|
|
from pathlib import Path
|
|
import subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
s=(root/'src/server/game/World/AdventurePopulationMgr.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]
|
|
code=r'''
|
|
#include "AdventurePopulationPolicy.h"
|
|
#include <cassert>
|
|
#include <vector>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <string>
|
|
using uint32=unsigned;using uint64=unsigned long long;
|
|
using namespace AdventurePopulationPolicy;
|
|
namespace GameTime{time_t now=100000;time_t GetGameTime(){return now;}}
|
|
struct ObjectGuid{enum{Player};unsigned id;ObjectGuid(int,unsigned i):id(i){} unsigned GetCounter(){return id;}};
|
|
namespace HighGuid{constexpr int Player=0;}
|
|
namespace ObjectAccessor{std::unordered_set<unsigned>connected;bool FindConnectedPlayer(ObjectGuid g){return connected.count(g.id);}}
|
|
enum class ClientlessPlayerState{Offline,Loading,Online,Removing};
|
|
struct World{std::unordered_map<unsigned,ClientlessPlayerState>states;auto GetClientlessPlayerState(unsigned id){return states[id];}}world;auto sWorld=&world;
|
|
struct Core{bool succeed=true;std::vector<unsigned>started;bool StartPlayer(unsigned,unsigned guid,int){started.push_back(guid);if(succeed)world.states[guid]=ClientlessPlayerState::Loading;return succeed;}}core;auto sClientlessPlayerMgr=&core;
|
|
int CreateClientlessAdventureLuaController(){return 0;}
|
|
float sample=.5;float frand(int,int){return sample;}
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
struct Profile{unsigned ProfileVersion=0,Class=0,Level=0;std::string SourcePath;};
|
|
struct Player{ObjectGuid GetGUID(){return ObjectGuid{ObjectGuid::Player,26};}};
|
|
struct Lua{Profile* profile=nullptr;Profile* GetAdventureProfile(unsigned){return profile;}}lua;auto sClientlessLuaEngine=&lua;
|
|
struct AdventurePopulationMgr{
|
|
struct Bot{uint32 Guid,Account,Pool;uint64 Created;uint32 Failures=0;time_t Retry=0;};
|
|
unsigned _cursor=0,_rotationAge=86400;double _rotationChance=.15;
|
|
std::array<unsigned,4>_targets{};std::vector<Bot>_bots;std::unordered_set<unsigned>_considered;
|
|
std::vector<std::pair<unsigned,unsigned>>generated;
|
|
std::unordered_map<unsigned,unsigned> _checks;void Online(Player*);
|
|
void Step();void Generate(unsigned pool,unsigned replace=0){generated.push_back({pool,replace});}
|
|
};
|
|
'''+function('void AdventurePopulationMgr::Step()')+function('void AdventurePopulationMgr::Online(')+r'''
|
|
int main(){
|
|
assert(Pool(1,true)==0 && Pool(1,false)==1 && Pool(2,true)==2 && Pool(2,false)==3);
|
|
assert(!Replace(true,true,false,100,0,0,1));assert(!Replace(false,false,false,100,0,0,1));
|
|
assert(!Replace(false,true,true,100,0,0,1));assert(!Replace(false,true,false,99,100,0,1));
|
|
assert(Replace(false,true,false,100,100,.1,.2));assert(!Replace(false,true,false,100,0,.3,.2));
|
|
assert(!Deficit(5,3,2) && Deficit(5,2,1)==2 && !Deficit(0,2,0));
|
|
AdventurePopulationMgr mgr;
|
|
Player named;Profile profile;lua.profile=&profile;
|
|
mgr.Online(&named);assert(mgr._checks.empty());
|
|
profile.ProfileVersion=2;profile.Class=1;profile.Level=60;profile.SourcePath="profiles/arthoran.lua";
|
|
mgr.Online(&named);assert(mgr._checks.count(26));
|
|
mgr._checks.clear();profile.Level=0;mgr.Online(&named);assert(mgr._checks.empty());
|
|
profile.Level=60;profile.ProfileVersion=0;mgr.Online(&named);assert(mgr._checks.empty());
|
|
lua.profile=nullptr;mgr.Online(&named);assert(mgr._checks.empty());
|
|
mgr.Step();assert(mgr.generated.empty() && core.started.empty());
|
|
mgr._targets={1,2,3,4};
|
|
mgr.Step();assert(mgr.generated.back().first==0);mgr.Step();assert(mgr.generated.back().first==1);
|
|
mgr.Step();assert(mgr.generated.back().first==2);mgr.Step();assert(mgr.generated.back().first==3);
|
|
mgr={};mgr._targets={1,0,0,0};mgr._bots={{10,6,0,0},{11,6,0,0},{12,6,2,0}};
|
|
world.states[10]=ClientlessPlayerState::Online;mgr.Step();assert(core.started.empty());
|
|
world.states[10]=ClientlessPlayerState::Loading;mgr.Step();assert(core.started.empty());
|
|
world.states[10]=ClientlessPlayerState::Offline;mgr.Step();assert(core.started.back()==10);assert(mgr.generated.empty());
|
|
world.states[10]=ClientlessPlayerState::Offline;core.succeed=false;core.started.clear();
|
|
mgr._bots[0].Retry=0;mgr.Step();assert(core.started.size()==1 && mgr._bots[0].Failures==1);
|
|
mgr.Step();assert(core.started.back()==11 && mgr.generated.empty()); // retry other saved candidate
|
|
mgr.Step();assert(core.started.size()==2 && mgr.generated.empty()); // no unbounded regeneration
|
|
mgr={};world.states.clear();core.started.clear();core.succeed=true;
|
|
mgr._targets={0,1,0,0};mgr._bots={{20,6,1,1}};sample=0;mgr.Step();
|
|
assert(mgr.generated.size()==1 && mgr.generated[0].second==20 && core.started.empty());
|
|
mgr.Step();assert(core.started.back()==20); // considered once per boot, no replacement loop
|
|
mgr={};core.started.clear();world.states.clear();mgr._targets={1,0,0,0};mgr._bots={{30,6,0,1}};
|
|
mgr._rotationChance=1;mgr.Step();assert(mgr.generated.empty() && core.started.back()==30); // persistence absolute
|
|
mgr={};core.started.clear();world.states.clear();mgr._targets={0,0,0,1};mgr._bots={{40,6,3,1}};
|
|
ObjectAccessor::connected.insert(40);mgr.Step();assert(core.started.empty() && mgr.generated.empty()); // real connected player protected
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as d:
|
|
p=Path(d);(p/'test.cpp').write_text(code)
|
|
subprocess.run(['g++','-std=c++17','-I',str(root/'src/server/game/World'),str(p/'test.cpp'),'-o',str(p/'test')],check=True)
|
|
subprocess.run([str(p/'test')],check=True)
|
|
print('Production population: four independent faction/persistence targets, fair selection, pending reservations, retries, existing inventory, once-per-boot rotation and connected/persistent protection passed')
|