Files
Viano-Core/tests/clientless/test_adventure_instance_loader.py
T
Nico 649915e575
clang PCH / build (push) Waiting to run
GCC PCH / build (push) Waiting to run
Fix AdventureBot travel recovery, area eligibility and safe build logout
2026-10-30 12:43:22 +01:00

58 lines
4.3 KiB
Python

"""Execute production legacy instance recovery without bypassing bot quotas."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
s=(root/'src/server/scripts/Custom/viano_bot_commands.cpp').read_text()
a=s.index('class ClientlessInstanceVisitResume : public WorldScript');b=s.index('\nvoid AddSC_viano_bot_commands()',a)
code=r'''
#include <cassert>
#include <deque>
#include <functional>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
using uint32=unsigned;
struct WorldScript{WorldScript(char const*){}virtual~WorldScript()=default;virtual void OnStartup(){}virtual void OnUpdate(unsigned){}};
struct Field{unsigned v=0;unsigned GetUInt32(){return v;}};
struct Rows{std::vector<std::pair<unsigned,unsigned>>data;unsigned index=0;Field f[2];Field*Fetch(){f[0].v=data[index].first;f[1].v=data[index].second;return f;}bool NextRow(){return ++index<data.size();}};
using QueryResult=std::shared_ptr<Rows>;
struct Callback{std::function<void()>run;};
struct Query{QueryResult rows;template<class F>Callback WithCallback(F f){return {[f,rows=rows]{f(rows);}};}};
struct QueryCallbackProcessor{std::vector<Callback>work;void AddCallback(Callback c){work.push_back(c);}void ProcessReadyCallbacks(){auto batch=std::move(work);work.clear();for(auto&c:batch)c.run();}};
struct Database{
bool visits=true,generated=true;std::vector<std::string>queries;
Query AsyncQuery(char const*text){std::string s=text;queries.push_back(s);if(s.find("SHOW TABLES")!=std::string::npos){bool exists=s.find("generated_bot")!=std::string::npos?generated:visits;return {exists?std::make_shared<Rows>():nullptr};}
auto r=std::make_shared<Rows>();r->data={{6,26},{6,40}};if(s.find("g.character_guid IS NULL")==std::string::npos)r->data.push_back({8,36});return {r};}
}CharacterDatabase;
enum class ClientlessPlayerState{Offline,Online};
struct World{std::unordered_map<unsigned,ClientlessPlayerState>states;auto GetClientlessPlayerState(unsigned id){return states[id];}}world;auto sWorld=&world;
struct Core{std::vector<unsigned>starts;void StartPlayer(unsigned,unsigned guid,int){starts.push_back(guid);world.states[guid]=ClientlessPlayerState::Online;}}core;auto sClientlessPlayerMgr=&core;
int CreateClientlessAdventureLuaController(){return 0;}
namespace ClientlessSafeLogout{void Update(){}}
namespace ClientlessCombatPreparation{unsigned updates=0;void Update(unsigned){++updates;}}
'''+s[a:b]+r'''
int main(){
ClientlessInstanceVisitResume script;script.OnStartup();assert(CharacterDatabase.queries.empty());
script.OnUpdate(9999);assert(CharacterDatabase.queries.empty());script.OnUpdate(1);assert(CharacterDatabase.queries.size()==1 && core.starts.empty());
script.OnUpdate(1);script.OnUpdate(1);script.OnUpdate(1);assert(CharacterDatabase.queries.size()==3 && core.starts.empty());
script.OnUpdate(1997);assert(core.starts==std::vector<unsigned>{26});script.OnUpdate(1999);assert(core.starts.size()==1);script.OnUpdate(1);assert((core.starts==std::vector<unsigned>{26,40}));
script.OnUpdate(2000);assert(core.starts.size()==2); // generated36 belongs exclusively to the population loader
assert(CharacterDatabase.queries.back().find("g.character_guid IS NULL")!=std::string::npos);
assert(ClientlessCombatPreparation::updates>0);
// Backward compatibility: a server without generator schema still resumes manual visits.
CharacterDatabase={};CharacterDatabase.generated=false;world={};core={};
ClientlessInstanceVisitResume legacy;legacy.OnStartup();legacy.OnUpdate(10000);for(int i=0;i<5;++i)legacy.OnUpdate(2000);
assert((core.starts==std::vector<unsigned>{26,40,36}));
// Missing optional visit schema never blocks the world or starts characters.
CharacterDatabase={};CharacterDatabase.visits=false;world={};core={};
ClientlessInstanceVisitResume absent;absent.OnStartup();absent.OnUpdate(10000);for(int i=0;i<5;++i)absent.OnUpdate(2000);assert(core.starts.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 instance recovery: no startup DB/login work, asynchronous delayed/paced manual recovery, generated-bot quota ownership, absent-schema compatibility and existing preparation ticks passed')