57 lines
4.2 KiB
Python
57 lines
4.2 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 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')
|