95 lines
5.8 KiB
Python
95 lines
5.8 KiB
Python
"""Run production save/restore with a transactional character-DB adapter."""
|
|
from pathlib import Path
|
|
import subprocess,tempfile
|
|
root=Path(__file__).resolve().parents[2]
|
|
s=(root/'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
|
|
def function(signature):
|
|
a=s.index(signature);b=s.index('{',a);n=b+1;depth=1
|
|
while depth:
|
|
depth+=(s[n]=='{')-(s[n]=='}');n+=1
|
|
return s[a:n]
|
|
code=r'''
|
|
#include "AdventureInstancePolicy.h"
|
|
struct MapEntry{bool IsRaid(){return false;}}mapEntry;struct Maps{MapEntry*LookupEntry(unsigned){return &mapEntry;}}sMapStore;
|
|
#include <cassert>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <cstdint>
|
|
using uint32=uint32_t;using uint64=uint64_t;using uint8=uint8_t;
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
#define TC_LOG_ERROR(...) ((void)0)
|
|
constexpr int UNIT_STAND_STATE_STAND=0;
|
|
std::string str(char const* s){return s;}template<class T>std::string str(T n){return std::to_string(n);}
|
|
struct Field{std::string value;std::string GetString(){return value;}uint8 GetUInt8(){return std::stoul(value);}uint32 GetUInt32(){return std::stoul(value);}uint64 GetUInt64(){return std::stoull(value);}};
|
|
struct Row{std::vector<Field> fields;Field* Fetch(){return fields.data();}};
|
|
using Result=std::shared_ptr<Row>;
|
|
struct Operation{std::string sql;std::vector<std::string> args;};
|
|
struct Transaction{std::vector<Operation> ops;template<class... A>void PAppend(char const* sql,A... args){ops.push_back({sql,{str(args)...}});}};
|
|
using Tx=std::shared_ptr<Transaction>;
|
|
struct DB{
|
|
bool fail=false,table=true;std::vector<std::string> row;std::vector<std::string> character;std::vector<Operation> last;
|
|
Tx BeginTransaction(){return std::make_shared<Transaction>();}void EscapeString(std::string&){}
|
|
struct Future{bool success;bool get(){return success;}};
|
|
struct Commit{Future m_future;};
|
|
Commit AsyncCommitTransaction(Tx& tx){last=tx->ops;if(fail)return {{false}};for(auto const& op:tx->ops){
|
|
if(op.sql=="CHARACTER_SAVE")character=op.args;
|
|
else if(op.sql.find("REPLACE INTO")==0)row=op.args;
|
|
else if(op.sql.find("DELETE FROM")==0)row.clear();
|
|
}return {{true}};}
|
|
Result Query(char const*){return table?std::make_shared<Row>():nullptr;}
|
|
Result PQuery(char const* sql,uint32){if(row.empty())return nullptr;auto r=std::make_shared<Row>();
|
|
if(std::string(sql).find("SELECT phase,")==0)for(auto i:{8,6,10})r->fields.push_back({row.at(i)});
|
|
else for(unsigned i=1;i<row.size();++i)r->fields.push_back({row[i]});
|
|
return r;
|
|
}
|
|
}CharacterDatabase;
|
|
struct Player{
|
|
unsigned GetDifficulty(bool){return 0;}
|
|
uint32 health=100,map=36,instance=7;bool teleport=false;int stand=0;
|
|
struct Guid{uint32 GetCounter(){return 26;}};Guid GetGUID(){return {};}
|
|
bool IsBeingTeleported(){return teleport;}uint32 GetMapId(){return map;}uint32 GetInstanceId(){return instance;}
|
|
void SaveToDB(Tx& tx){tx->PAppend("CHARACTER_SAVE",health,map,instance);}
|
|
void SetStandState(int v){stand=v;}
|
|
};
|
|
struct Location{unsigned InstanceDifficulty=1;};struct Locations{Location l;Location*Find(std::string const&){return &l;}}locations;auto sAdventureLocationMgr=&locations;
|
|
struct AdventureBehaviorMgr{
|
|
enum class Phase{Idle,OnSite};
|
|
struct Record{AdventureInstancePolicy::Visit Instance;bool Schedule=false,Dirty=false;Phase State=Phase::Idle;
|
|
std::string Intent,WantedIntent,Location,WantedLocation,Detail;time_t Deadline=0;};
|
|
bool SaveInstance(Player*,Record&);bool RestoreInstance(Player*,Record&);void FinishInstance(Player*,Record&);
|
|
};
|
|
'''
|
|
for signature in ('bool AdventureBehaviorMgr::SaveInstance(', 'bool AdventureBehaviorMgr::RestoreInstance(', 'void AdventureBehaviorMgr::FinishInstance('):code+=function(signature)+'\n'
|
|
code+=r'''
|
|
int main(){
|
|
AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p;
|
|
assert(!mgr.RestoreInstance(&p,r));
|
|
auto& v=r.Instance;v.Active=true;v.Location="instance_visit_at_78";v.Entry=78;v.Exit=121;v.Map=36;v.InstanceId=7;
|
|
v.BlockEnd=3600;v.PrepareUntil=2410;v.Stage=AdventureInstancePolicy::Phase::Inside;v.LossPercent=37;r.Schedule=true;
|
|
assert(mgr.SaveInstance(&p,r));assert(CharacterDatabase.last.size()==2);
|
|
assert(CharacterDatabase.character[0]=="100"&&CharacterDatabase.character[2]=="7");
|
|
// Restart creates an empty behavior record, then restores the actual visit.
|
|
AdventureBehaviorMgr::Record restored;assert(mgr.RestoreInstance(&p,restored));
|
|
assert(restored.Intent=="instance"&&restored.Location==v.Location&&restored.Deadline==3600&&restored.Schedule);
|
|
assert(restored.Instance.Stage==AdventureInstancePolicy::Phase::Inside&&restored.Instance.InstanceId==7);
|
|
assert(restored.Instance.LossPercent==37&&!restored.Instance.InjuryApplied);
|
|
// Character health and the once-only marker commit together, never separately.
|
|
v.Stage=AdventureInstancePolicy::Phase::Rest;v.InjuryApplied=true;p.health=63;p.map=0;p.instance=0;
|
|
CharacterDatabase.fail=true;assert(!mgr.SaveInstance(&p,r));
|
|
assert(CharacterDatabase.character[0]=="100");assert(mgr.RestoreInstance(&p,restored)&&!restored.Instance.InjuryApplied);
|
|
CharacterDatabase.fail=false;assert(mgr.SaveInstance(&p,r));
|
|
assert(CharacterDatabase.character[0]=="63"&&CharacterDatabase.character[1]=="0");
|
|
assert(mgr.RestoreInstance(&p,restored)&&restored.Instance.InjuryApplied&&restored.Deadline==3600);
|
|
// Clear the visit atomically with final character state, and release a queued intent.
|
|
r.Dirty=true;r.State=AdventureBehaviorMgr::Phase::OnSite;mgr.FinishInstance(&p,r);
|
|
assert(!r.Instance.Active&&r.State==AdventureBehaviorMgr::Phase::Idle&&CharacterDatabase.row.empty());
|
|
assert(!mgr.RestoreInstance(&p,restored));
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
src=Path(tmp)/'save.cpp';exe=Path(tmp)/'save';src.write_text(code)
|
|
subprocess.run(['g++','-std=c++17','-I'+str(root/'src/server/game/World'),str(src),'-o',str(exe)],check=True)
|
|
subprocess.run([str(exe)],check=True)
|
|
print('Production instance persistence: phases, original deadline, instance ID, transactional HP marker, failed commit and intent release passed.')
|