Implement AdventureBot profiles, daily planning and population management
This commit is contained in:
1 parent
d6fea6ee68
commit
d3273c677e
61 files changed
+2280
-116
No files matched your search
@@ -0,0 +1,56 @@
|
||||
"""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')
|
||||
Reference in new issue
Block a user