153 lines
8.3 KiB
Python
153 lines
8.3 KiB
Python
"""Exercise production recovery selection/clock with native navigation adapters."""
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
|
|
root = Path(__file__).resolve().parents[2]
|
|
source = (root / 'src/server/game/World/AdventureBehaviorMgr.cpp').read_text()
|
|
|
|
def function(signature):
|
|
start = source.index(signature)
|
|
body = source.index('{', start)
|
|
end, depth = body + 1, 1
|
|
while depth:
|
|
depth += (source[end] == '{') - (source[end] == '}')
|
|
end += 1
|
|
return source[start:end]
|
|
|
|
code = r'''
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <string>
|
|
#include <vector>
|
|
using uint32=uint32_t;
|
|
#define TC_LOG_INFO(...) ((void)0)
|
|
constexpr int UNIT_STAND_STATE_SLEEP=3,UNIT_STAND_STATE_SIT=1;
|
|
struct Position {
|
|
float x=0,y=0,z=0,o=0;
|
|
Position()=default; Position(float a,float b,float c,float d=0):x(a),y(b),z(c),o(d){}
|
|
float GetOrientation()const{return o;}
|
|
float GetExactDist(Position const& p)const{return GetExactDist(p.x,p.y,p.z);}
|
|
float GetPositionZ()const{return z;}
|
|
float GetExactDist(float a,float b,float c)const{return std::sqrt((x-a)*(x-a)+(y-b)*(y-b)+(z-c)*(z-c));}
|
|
};
|
|
struct Guid{uint32 GetCounter(){return 26;}};
|
|
struct Player:Position {
|
|
uint32 map=0;bool water=false,falling=false,phase=true;int stand=0,poses=0;
|
|
bool space=true;
|
|
std::vector<Player*> occupants;
|
|
uint32 GetMapId(){return map;}Guid GetGUID(){return {};}
|
|
bool IsInWater(){return water;}bool IsFalling(){return falling;}
|
|
void GetPlayerListInGrid(std::vector<Player*>& out,float){out=occupants;}
|
|
bool IsInPhase(Player* p){return p->phase;}
|
|
void SetFacingTo(float){}void SetStandState(int value){stand=value;++poses;}
|
|
int GetStandState(){return stand;}
|
|
};
|
|
bool SleepSpace(Player* p,Position const&){return p->space;}
|
|
std::vector<Position> candidates;
|
|
std::vector<Position> SleepCandidates(Player*,Position const&,bool){return candidates;}
|
|
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float FinalDistance=0;};
|
|
double Novelty(std::vector<std::string> const&,std::string const&){return 1;}
|
|
void Remember(std::vector<std::string>& h,std::string const& s){h.push_back(s);}
|
|
std::size_t Choose(std::vector<double> const& weights,int&){for(std::size_t i=0;i<weights.size();++i)if(weights[i]>0)return i;return weights.size();}
|
|
struct AdventureMicroAction{std::string Requirement;};
|
|
struct AdventureLocation {
|
|
struct {std::string Id="bed",Category="rest_sleep";uint32 MapId=0;float X=0,Y=0,Z=0,O=0;}Point;
|
|
bool allowed=true;
|
|
};
|
|
struct Locations {
|
|
std::vector<AdventureLocation> points;
|
|
auto const& Locations()const{return points;}
|
|
AdventureLocation const* Find(std::string const& id){for(auto const& p:points)if(p.Point.Id==id)return &p;return nullptr;}
|
|
}locations;
|
|
auto sAdventureLocationMgr=&locations;
|
|
enum class ClientlessTravelState{Moving,Arrived,PathRejected};
|
|
struct Travel {
|
|
struct Status{ClientlessTravelState State=ClientlessTravelState::Moving;}status;
|
|
float endpointError=0;bool suspicious=false;
|
|
bool AnalyzePath(Player*,Position const&,ClientlessPathDiagnostics& d){d.FinalDistance=endpointError;d.Suspicious=suspicious;return true;}
|
|
bool present=true;Status const* GetStatus(uint32){return present?&status:nullptr;}
|
|
}travel;
|
|
auto sClientlessTravelMgr=&travel;
|
|
class AdventureBehaviorMgr {
|
|
public:
|
|
struct RecoveryScene{bool Active=true,Approaching=true;std::string Point="bed",Posture="sleep";uint32 Remaining=3000,ApproachElapsed=0;Position Destination;bool HasDestination=false;uint32 SpaceCheckMs=0;};
|
|
struct Record{Position Anchor;uint32 AnchorMap=0;RecoveryScene Recovery;std::string Detail;std::vector<std::string> TargetHistory;int Random=0;};
|
|
bool Allowed(Player*,AdventureLocation const& p,std::string const&)const{return p.allowed;}
|
|
std::vector<Position> moves;bool moveOK=true;
|
|
bool LocalMove(Player*,Record&,Position const& p,int){moves.push_back(p);return moveOK;}
|
|
bool StartRecovery(Player*,Record&,AdventureMicroAction const&,uint32);
|
|
bool RecoveryAvailable(Player*,Record const&,AdventureMicroAction const&)const;
|
|
bool UpdateRecovery(Player*,Record&,uint32);
|
|
};
|
|
'''
|
|
# Avoid a class/member name collision in the adapter.
|
|
code = code.replace('struct Locations {', 'struct LocationStore {')
|
|
code += function('bool AdventureBehaviorMgr::RecoveryAvailable(')
|
|
code += function('bool AdventureBehaviorMgr::StartRecovery(')
|
|
code += function('bool AdventureBehaviorMgr::UpdateRecovery(')
|
|
code += r'''
|
|
int main(){
|
|
AdventureBehaviorMgr mgr;AdventureBehaviorMgr::Record r;Player p;
|
|
AdventureMicroAction sleep{"sleep"},ground{"ground"},unknown{"anything"};
|
|
assert(!mgr.RecoveryAvailable(&p,r,sleep));
|
|
locations.points.push_back({});
|
|
assert(mgr.RecoveryAvailable(&p,r,sleep));
|
|
assert(!mgr.RecoveryAvailable(&p,r,ground)&&!mgr.RecoveryAvailable(&p,r,unknown));
|
|
// Reject endpoints beneath an elevated surface before starting movement.
|
|
candidates={Position(3,0,0)};travel.endpointError=0.7f;
|
|
assert(!mgr.StartRecovery(&p,r,sleep,7000)&&mgr.moves.empty());
|
|
travel.endpointError=0;assert(mgr.StartRecovery(&p,r,sleep,7000));
|
|
assert(r.Recovery.HasDestination && r.Recovery.Destination.x==3 && mgr.moves.size()==1);
|
|
assert(r.Recovery.Remaining==7000);
|
|
mgr.moves.clear();r=AdventureBehaviorMgr::Record{};
|
|
// Even if a tavern is first in the catalog, explicit sleep anchors have priority.
|
|
locations.points.insert(locations.points.begin(),AdventureLocation{});locations.points[0].Point.Id="inn";
|
|
locations.points[0].Point.Category="tavern";
|
|
assert(mgr.StartRecovery(&p,r,sleep,7000)&&r.Recovery.Point=="bed");
|
|
locations.points.erase(locations.points.begin());r=AdventureBehaviorMgr::Record{};
|
|
auto& point=locations.points[0];
|
|
point.Point.Category="tavern";assert(mgr.RecoveryAvailable(&p,r,sleep)); // bounded indoor floor search
|
|
point.Point.Category="rest_sleep";point.Point.MapId=1;assert(!mgr.RecoveryAvailable(&p,r,sleep));
|
|
point.Point.MapId=0;point.allowed=false;assert(!mgr.RecoveryAvailable(&p,r,sleep));
|
|
point.allowed=true;point.Point.X=31;assert(!mgr.RecoveryAvailable(&p,r,sleep));
|
|
point.Point.X=0;point.Point.Z=13;assert(!mgr.RecoveryAvailable(&p,r,sleep));point.Point.Z=0;
|
|
// Walking cannot consume sleep time or trigger the sleep pose.
|
|
for(int n=0;n<10;++n)assert(mgr.UpdateRecovery(&p,r,1000));
|
|
assert(p.poses==0&&r.Recovery.Remaining==3000);
|
|
travel.status.State=ClientlessTravelState::Arrived;p.x=2;
|
|
assert(!mgr.UpdateRecovery(&p,r,1000)&&p.poses==0);p.x=0;
|
|
Player occupant;p.occupants={&occupant};
|
|
assert(!mgr.UpdateRecovery(&p,r,1000)&&p.poses==0);p.occupants.clear();
|
|
assert(mgr.UpdateRecovery(&p,r,1000)&&p.stand==UNIT_STAND_STATE_SLEEP&&!r.Recovery.Approaching);
|
|
assert(mgr.UpdateRecovery(&p,r,1000));assert(mgr.UpdateRecovery(&p,r,1000));
|
|
assert(!mgr.UpdateRecovery(&p,r,1000)); // full duration starts after arrival
|
|
r.Recovery={};p.space=false;assert(!mgr.UpdateRecovery(&p,r,1000)&&r.Recovery.Approaching);p.space=true;
|
|
r.Recovery.Destination=Position(4,0,0);r.Recovery.HasDestination=true;
|
|
assert(!mgr.UpdateRecovery(&p,r,1000));p.x=4;
|
|
assert(mgr.UpdateRecovery(&p,r,1000)); // chosen offset, not the old obstructed marker
|
|
p.space=false;r.Recovery.SpaceCheckMs=0;assert(!mgr.UpdateRecovery(&p,r,1000));p.space=true;p.x=0;
|
|
r.Recovery={};travel.status.State=ClientlessTravelState::PathRejected;
|
|
assert(!mgr.UpdateRecovery(&p,r,1000));
|
|
travel.status.State=ClientlessTravelState::Moving;r.Recovery.ApproachElapsed=60000;
|
|
assert(!mgr.UpdateRecovery(&p,r,1000));
|
|
r.Recovery={};travel.status.State=ClientlessTravelState::Arrived;
|
|
locations.points.clear();assert(!mgr.UpdateRecovery(&p,r,1000)); // removed on catalog reload
|
|
locations.points.push_back({});p.water=true;assert(!mgr.UpdateRecovery(&p,r,1000));p.water=false;
|
|
p.falling=true;assert(!mgr.UpdateRecovery(&p,r,1000));p.falling=false;
|
|
p.map=1;assert(!mgr.UpdateRecovery(&p,r,1000));p.map=0;
|
|
r.Recovery.Posture="ground";assert(!mgr.UpdateRecovery(&p,r,1000));
|
|
locations.points[0].Point.Category="rest_ground";
|
|
assert(mgr.UpdateRecovery(&p,r,1000)&&p.stand==UNIT_STAND_STATE_SIT);
|
|
p.stand=0;assert(!mgr.UpdateRecovery(&p,r,1000)); // externally interrupted posture
|
|
}
|
|
'''
|
|
with tempfile.TemporaryDirectory(prefix='vcore-rest-') as folder:
|
|
cpp, binary = Path(folder) / 'test.cpp', Path(folder) / 'test'
|
|
cpp.write_text(code)
|
|
subprocess.run(['c++', '-std=c++17', str(cpp), '-o', str(binary)], check=True)
|
|
subprocess.run([str(binary)], check=True)
|
|
print('Recovery: authored places, bounds, arrival, occupancy, full rest duration, failure, reload and interruption passed.')
|