Files
Viano-Core/tests/clientless/test_adventure_economy.py
T
Nico c0c799c4b6
GCC PCH / build (push) Canceled after 0s
clang PCH / build (push) Canceled after 0s
Add AdventureBot combat reactions, training and corpse recovery
2026-10-27 23:17:05 +01:00

107 lines
7.7 KiB
Python

"""Execute economic role selection against production discovery and cue policy."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/AdventureSocialActions.cpp').read_text()
def function(sig):
a=source.index(sig);b=source.index('{',a);depth=1;e=b+1
while depth:depth+=(source[e]=='{')-(source[e]=='}');e+=1
return source[a:e]
code=r'''
#include "AdventureEconomyPolicy.h"
#include <algorithm>
#include "AdventureProfessionPolicy.h"
#include <cassert>
#include <string>
#include <vector>
#include <cmath>
constexpr int UNIT_NPC_FLAGS=0,UNIT_NPC_FLAG_AUCTIONEER=2097152;
enum class HighGuid{Unit};
struct Guid{int value=1;Guid(int v=1):value(v){}Guid(HighGuid,unsigned,unsigned low):value(low){}unsigned GetCounter(){return value;}bool IsEmpty(){return value==0;}};
using ObjectGuid=Guid;
struct WorldObject{
void* ToGameObject(){return nullptr;}
bool inWorld=true,phase=true;unsigned map=0;float x=0,z=0;
bool IsInWorld()const{return inWorld;}unsigned GetMapId()const{return map;}float GetPositionX()const{return x;}float GetPositionY()const{return 0;}float GetPositionZ()const{return z;}float GetCollisionHeight()const{return 2;}
};
struct Unit:WorldObject{
bool repair=false,vendor=false,banker=false,auction=false,inn=false,alive=true,combat=false,flying=false,transit=false,critter=false,totem=false,friendly=true,spline=false;Guid owner{0},guid;
bool IsArmorer(){return repair;}bool IsVendor(){return vendor;}bool IsBanker(){return banker;}bool HasFlag(int,int){return auction;}bool IsInnkeeper(){return inn;}
bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool IsInFlight(){return transit;}bool IsFlying(){return flying;}bool GetTransport(){return transit;}
bool IsCritter(){return critter;}bool IsTotem(){return totem;}Guid GetCharmerOrOwnerGUID(){return owner;}Guid GetGUID(){return guid;}bool IsSplineEnabled(){return spline;}
};
struct Creature:Unit{};
namespace VMAP{enum class ModelIgnoreFlags{Nothing};}
constexpr int LINEOFSIGHT_ALL_CHECKS=0;
struct NativeMap{bool clear=true;int queries=0;bool isInLineOfSight(int,float,float,float fromZ,float,float,float toZ,int,VMAP::ModelIgnoreFlags){++queries;assert(fromZ==2&&toZ==2);return clear;}}nativeMap;
struct Player:Unit{
NativeMap* GetMap(){return &nativeMap;}int GetPhaseShift(){return 0;}
std::vector<Creature*> creatures;std::vector<Player*> players;int playerQueries=0;bool los=true;
bool IsInPhase(WorldObject const* u){return u->phase;}bool IsFriendlyTo(Unit*u){return u->friendly;}bool IsWithinLOSInMap(WorldObject*){return los;}
void GetCreatureListWithEntryInGrid(std::vector<Creature*>&out,int,float){out=creatures;}
void GetPlayerListInGrid(std::vector<Player*>&out,float){out=players;++playerQueries;}
};
struct Anchor{float GetExactDist(WorldObject const*u)const{return std::hypot(u->x,u->z);}float GetPositionZ()const{return 0;}};
struct AdventureSocialContext{Anchor Anchor;unsigned MapId=0;std::string ServiceKind,Intent="economy";};
enum class ClientlessEntityType{Creature,Player};
constexpr unsigned CLIENTLESS_CAP_REPAIR=256,CLIENTLESS_CAP_VENDOR=1,CLIENTLESS_CAP_BANKER=8,CLIENTLESS_CAP_AUCTIONEER=32;
struct Entity{ClientlessEntityType Type=ClientlessEntityType::Creature;bool Alive=true;unsigned Capabilities=0,Entry=0,Guid=0;};
struct Snapshot{float Radius=0;unsigned ScanAgeMs=1000;std::vector<Entity> Entities;};
Player* observer=nullptr;
struct Perception{Snapshot snapshot;Snapshot* GetSnapshot(unsigned){return &snapshot;}
void ScanNow(Player*p,float radius){observer=p;snapshot={};snapshot.Radius=radius;
for(auto u:p->creatures)if(std::hypot(u->x,u->z)<=radius) snapshot.Entities.push_back({ClientlessEntityType::Creature,u->alive,unsigned(u->vendor?1:0)|unsigned(u->banker?8:0)|unsigned(u->auction?32:0)|unsigned(u->repair?256:0),0,unsigned(u->guid.value)});
}}perception;auto sClientlessPerceptionMgr=&perception;
namespace ObjectAccessor{Creature* GetCreature(Player&p,ObjectGuid guid){for(auto u:p.creatures)if(u->guid.value==guid.value)return u;return nullptr;}}
struct Managed{bool IsManaged(unsigned){return true;}}managed;auto sClientlessPlayerMgr=&managed;
'''.replace('Anchor Anchor;','struct Anchor Anchor;')
for sig in ('bool InBounds(', 'bool Friendly(', 'bool ServiceMatches(', 'bool TargetVisible(', 'std::vector < Unit * > People('):code+=function(sig)+'\n'
code+=r'''
int main(){
using namespace AdventureEconomyPolicy;using namespace AdventureSocialFlow;
Player p,bot;Creature vendor,banker,auction,inn,guard,repair;
repair.repair=true;vendor.vendor=true;banker.banker=true;auction.auction=true;inn.inn=true;
p.creatures={&vendor,&banker,&auction,&inn,&guard,&repair};int nextGuid=10;for(auto u:p.creatures)u->guid.value=++nextGuid;p.players={&bot};AdventureSocialContext c;
for(auto kind:{"vendor_visit","goods","trade","vendor_compare"}){
auto people=People(&p,kind,c);assert(people.size()==1&&people[0]==&vendor);
}
for(auto kind:{"bank_visit","banking"})assert(People(&p,kind,c)==std::vector<Unit*>{&banker});
for(auto kind:{"auction_visit","auctions"})assert(People(&p,kind,c)==std::vector<Unit*>{&auction});
for(auto kind:{"service_wait","service_finish"})assert(People(&p,kind,c).size()==4);
c.ServiceKind=LocationRequirement("economy_bank");
assert(People(&p,"goods",c).empty());assert(People(&p,"service_wait",c)==std::vector<Unit*>{&banker});
c.ServiceKind=LocationRequirement("economy_auction");
assert(People(&p,"banking",c).empty());assert(People(&p,"service_wait",c)==std::vector<Unit*>{&auction});
c.ServiceKind="";
vendor.x=75;assert(People(&p,"goods",c)==std::vector<Unit*>{&vendor});vendor.x=0;
assert(p.playerQueries==0); // Clientless bots are never mistaken for service NPCs.
vendor.friendly=false;assert(People(&p,"goods",c).empty());vendor.friendly=true;
vendor.alive=false;assert(People(&p,"goods",c).empty());vendor.alive=true;
vendor.combat=true;assert(People(&p,"goods",c).empty());vendor.combat=false;
vendor.owner.value=5;assert(People(&p,"goods",c).empty());vendor.owner.value=0;
vendor.phase=false;assert(People(&p,"goods",c).empty());vendor.phase=true;
vendor.x=201;assert(People(&p,"goods",c).empty());vendor.x=0;
vendor.z=26;assert(People(&p,"goods",c).empty());vendor.z=0;
p.los=false;assert(People(&p,"goods",c)==std::vector<Unit*>{&vendor});p.los=true; // Discovery precedes the MMAP approach and final LOS check.
p.los=false;nativeMap.clear=true;
assert(TargetVisible(&p,&banker,"banking"));assert(nativeMap.queries==1);
assert(!TargetVisible(&p,&banker,"npc")); // Ordinary social visibility is preserved.
nativeMap.clear=false;assert(!TargetVisible(&p,&banker,"banking")); // A real wall still blocks the scene.
nativeMap.clear=true;p.los=true;
assert(People(&p,"order",c)==std::vector<Unit*>{&inn}); // Preserve ordinary innkeeper interactions.
assert(People(&p,"repair",c)==std::vector<Unit*>{&repair});
for(auto kind:{"repair","vendor_visit","goods","trade","vendor_compare","bank_visit","banking","auction_visit","auctions"}){
assert(Economic(kind));auto steps=Steps(kind,20000);
assert(steps.front().Action==Cue::Greet&&steps.back().Action==Cue::Farewell);
for(auto step:steps)assert(step.Action!=Cue::Fire&&step.Action!=Cue::Dance&&step.Action!=Cue::Seat);
}
assert(!Economic("npc")&&Steps("unknown",10000).empty());
assert(RequiredService("supplies")==Service::None&&Economic("supplies"));
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-economy-') as folder:
cpp=Path(folder)/'test.cpp';binary=Path(folder)/'test';cpp.write_text(code)
subprocess.run(['c++','-std=c++17','-I',str(root/'src/server/game/World'),str(cpp),'-o',str(binary)],check=True)
subprocess.run([str(binary)],check=True)
print('Economy: native service roles, friendly/phase/LOS/bounds gates, no bot service targets and coherent performative cues passed.')