Files
Viano-Core/tests/clientless/test_adventure_rest.py
T
Nico e18a416bf4
clang PCH / build (push) Canceled after 0s
GCC PCH / build (push) Canceled after 0s
Add persistent instance visits and validate sleeping spaces
2026-10-27 20:10:13 +01:00

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.')