"""Run production social cues with native-adapter doubles and the real sequence clock.""" 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(signature): a=source.index(signature);b=source.index('{',a);depth=1;c=b+1 while depth: depth+=(source[c]=='{')-(source[c]=='}');c+=1 return source[a:c] code=r''' #include "AdventureSocialFlow.h" #include "AdventureEconomyPolicy.h" #include "AdventureExplorationPolicy.h" #include #include #include #include "AdventureProfessionPolicy.h" #include #include #include #include using uint32=uint32_t; using namespace AdventureSocialFlow; #define TC_LOG_INFO(...) ((void)0) constexpr float INVALID_HEIGHT=-100000; constexpr int EMOTE_STATE_USE_STANDING=69,EMOTE_STATE_WORK_MINING=233,EMOTE_STATE_WORK_CHOPWOOD=234,EMOTE_STATE_KNEEL=68; constexpr int CookingFire=818,UNIT_NPC_EMOTESTATE=0,UNIT_STAND_STATE_SIT=1,UNIT_STAND_STATE_SIT_LOW_CHAIR=4,CMSG_GAMEOBJ_USE=1; constexpr int EMOTE_ONESHOT_QUESTION=6,EMOTE_ONESHOT_POINT=25,EMOTE_ONESHOT_USE_STANDING=432; constexpr int EMOTE_ONESHOT_WAVE=3,EMOTE_ONESHOT_TALK=1,EMOTE_ONESHOT_YES=273,EMOTE_ONESHOT_CHEER=4,EMOTE_STATE_DANCE=10; constexpr int SPELL_VISUAL_KIT_DRINK=438,SPELL_VISUAL_KIT_FOOD=406,GAMEOBJECT_TYPE_CHAIR=7,SPELL_CAST_OK=0; struct ObjectGuid{int id=0;uint32 GetCounter(){return id;}bool operator==(ObjectGuid const& other)const{return id==other.id;}bool IsEmpty()const{return !id;}std::string ToString()const{return std::to_string(id);}}; struct Unit;struct GameObject;struct Player; struct WorldObject{ int kind=0;ObjectGuid guid{1};virtual ~WorldObject()=default; float x=0,y=0,z=0,heading=0;float GetPositionX(){return x;}float GetOrientation(){return heading;}float GetPositionY(){return y;}float GetPositionZ(){return z;} float GetAngle(WorldObject*p){return std::atan2(p->y-y,p->x-x);} Unit* ToUnit();GameObject* ToGameObject();ObjectGuid GetGUID(){return guid;} }; struct Unit:WorldObject{bool service=false;Unit(){kind=1;}}; struct GameObject:WorldObject{ struct Info{float size=1;struct{int triggeredEvent=0;uint32 chairslots=1,chairheight=1;}chair;}info; GameObject(){kind=2;guid.id=2;} bool spawned=true;bool isSpawned(){return spawned;} int GetGoType(){return GAMEOBJECT_TYPE_CHAIR;}Info* GetGOInfo(){return &info;} }; Unit* WorldObject::ToUnit(){return kind==1?static_cast(this):nullptr;} GameObject* WorldObject::ToGameObject(){return kind==2?static_cast(this):nullptr;} struct WorldPacket{WorldPacket(int,int){}WorldPacket& operator<<(ObjectGuid){return *this;}}; struct Session{int uses=0;void HandleGameObjectUseOpcode(WorldPacket&){++uses;}}; struct Position{float m_positionX=0,m_positionY=0,m_positionZ=0;void Relocate(Player*);float GetPositionX()const{return m_positionX;}float GetPositionY()const{return m_positionY;}float GetPositionZ()const{return m_positionZ;}}; struct NativeMap{float GetHeight(int,float,float,float,bool,float){return 0;}}nativeMap; struct Player:Unit{ Player(){x=2;} int repairs=0;void DurabilityRepairAll(bool cost,float,bool guild){assert(!cost&&!guild);++repairs;} std::vector emotes;int faces=0,casts=0;bool casting=false,sit=false,los=true;float distance=2;Session session; ObjectGuid selection; bool visible=true;bool CanSeeOrDetect(GameObject*){return visible;} NativeMap* GetMap(){return &nativeMap;}int GetPhaseShift(){return 0;} bool IsBeingTeleported(){return false;}bool IsSitState(){return sit;}bool IsNonMeleeSpellCast(bool){return casting;} float GetExactDist(Position const&){return distance;}float GetExactDist(WorldObject*){return distance;}void SetFacingToObject(WorldObject*){++faces;} std::vector headings;void SetFacingTo(float f){heading=f;headings.push_back(f);}int chairStand=5;uint32 GetStandState(){return sit?chairStand:0;} std::vector states;void SetSelection(ObjectGuid g){selection=g;}void SetUInt32Value(int,int state){states.push_back(state);} void HandleEmoteCommand(int id){emotes.push_back(id);}void SendPlaySpellVisualKit(int,int,int){} void SetStandState(int){sit=true;}int CastSpell(Player*,int,bool){++casts;casting=true;return SPELL_CAST_OK;} bool IsWithinLOSInMap(WorldObject*){return los;}bool IsWithinLOS(float,float,float){return los;}bool GetGameObjectIfCanInteractWith(ObjectGuid){return true;}Session* GetSession(){return &session;} }; void Position::Relocate(Player*p){m_positionX=p->x;m_positionY=p->y;m_positionZ=p->z;} struct AdventureSocialContext{bool AllowCreateFire=true;std::string Intent="social";AdventureProfessionPolicy::Professions PrimaryProfessions;Position Anchor;std::deque TargetHistory;int departures=0;bool reachable=true;bool Move(Position const&,float){++departures;return reachable;}}; struct AdventureSocialScene{ bool RepairDone=false; bool Active=true,Approaching=false,Casting=false,WaitingChair=false,Seated=false,Departing=false; std::string Action="social_npc",Kind="npc",TargetKey;ObjectGuid Target{1},PreviousSelection;Position Destination,Origin;float Facing=0; uint32 Elapsed=0,CastElapsed=0,CheckTimer=0,Repaths=0,ServiceVisits=0,ServiceDuration=30000;Sequence Sequence; }; WorldObject* target=nullptr;GameObject* fire=nullptr;bool friendly=true,approach=true; WorldObject* Resolve(Player*,ObjectGuid g){return g.id?target:nullptr;} bool InBounds(Player*,WorldObject* t,AdventureSocialContext&){return t!=nullptr;} bool Friendly(Player*,Unit*,AdventureSocialContext&){return friendly;} bool ServiceMatches(Unit* unit,std::string const&){return unit->service;} bool TargetVisible(Player* p,WorldObject* target,std::string const&){return p->IsWithinLOSInMap(target);} std::vector services; std::vector People(Player*,std::string const&,AdventureSocialContext&){return services;} templateT* Pick(Player*,std::vector const& choices,AdventureSocialContext&){return choices.empty()?nullptr:choices[0];} void Remember(std::deque& history,std::string key){history.push_back(key);} bool Approach(Player*,AdventureSocialScene& s,WorldObject*,AdventureSocialContext&){s.Approaching=true;return approach;} GameObject* Fire(Player*,AdventureSocialContext&){return fire;} bool CanMakeFire(Player*){return true;} void Say(Player*,std::string const&,AdventureSocialContext&){} uint32 Reaction(AdventureSocialContext&){return 11;} enum class ClientlessTravelState{Moving,Arrived,Rejected}; struct Travel{struct{ClientlessTravelState State=ClientlessTravelState::Moving;}status;auto GetStatus(uint32){return &status;}}travel;auto sClientlessTravelMgr=&travel; struct AdventureProfessionTargets{static bool Valid(Player*,WorldObject*,std::string const&,AdventureSocialContext const&){return true;}}; class AdventureSocialActions{public:static bool Update(Player*,AdventureSocialScene&,uint32,AdventureSocialContext&);}; '''.replace('Sequence Sequence;', 'AdventureSocialFlow::Sequence Sequence;') code+=function('bool OnChair(')+'\n'+function('bool AtFront(')+'\n'+function('std::vector < Step > Steps(')+'\n'+function('bool AdventureSocialActions::Update(') code+=r''' int main(){ for(float heading:{0.0f,1.5707963f,3.1415926f,4.7123890f,-3.1415926f}) { for(float offset:{0.0f,0.28f,-0.28f,0.55f,-0.55f}) assert(ConversationFront(10,20,heading,10+2.2f*std::cos(heading+offset),20+2.2f*std::sin(heading+offset))); assert(!ConversationFront(10,20,heading,10-2.2f*std::cos(heading),20-2.2f*std::sin(heading))); assert(!ConversationFront(10,20,heading,10+2.2f*std::cos(heading+1.5707963f),20+2.2f*std::sin(heading+1.5707963f))); } assert(!ConversationFront(0,0,0,0.2f,0)&&!ConversationFront(0,0,0,8,0)); AdventureSocialContext c;Unit npc;GameObject chair;Player p;AdventureSocialScene s;target=&npc; s.Sequence.Start(Steps("npc",30000));s.Approaching=true; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.empty()); travel.status.State=ClientlessTravelState::Arrived; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==1&&p.emotes[0]==EMOTE_ONESHOT_WAVE); bool active=true;for(unsigned i=0;i<60&&active;++i)active=AdventureSocialActions::Update(&p,s,1000,c); assert(!active);int talks=0,waves=0;for(auto id:p.emotes){talks+=id==1;waves+=id==3;}assert(talks==2&&waves==2); // Being close behind a partner must reposition, not start talking there. s={};s.Sequence.Start(Steps("npc",30000));p.x=-2;auto before=p.emotes.size(); assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==before&&s.Repaths>0);p.x=2; // Missing or newly hostile partners terminate the scene without more emotes. s={};s.Sequence.Start(Steps("npc",30000));target=nullptr;auto count=p.emotes.size(); assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==count); target=&npc;friendly=false;assert(!AdventureSocialActions::Update(&p,s,1000,c));friendly=true; // Chairs go through the native use handler once and must confirm sitting. s={};s.Target.id=2;s.Sequence.Start(Steps("seat",3000));target=&chair; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.session.uses==1&&s.WaitingChair); assert(!AdventureSocialActions::Update(&p,s,1000,c)); // no fabricated successful seat s={};s.Target.id=2;s.Sequence.Start(Steps("seat",3000));p.sit=true;p.x=0; assert(AdventureSocialActions::Update(&p,s,1000,c));assert(AdventureSocialActions::Update(&p,s,1000,c)&&s.Seated); // Native chair animation beside the actual slot is not successful seating. float seatError=0; assert(OnChair(&p,&chair,seatError));p.x=2;assert(!OnChair(&p,&chair,seatError));p.x=0; p.chairStand=UNIT_STAND_STATE_SIT;assert(!OnChair(&p,&chair,seatError));p.chairStand=5; p.z=1;assert(!OnChair(&p,&chair,seatError));p.z=0; chair.heading=1.5707963f;assert(!OnChair(&p,&chair,seatError));p.heading=chair.heading;assert(OnChair(&p,&chair,seatError));chair.heading=p.heading=0; chair.info.chair.chairslots=3;p.y=1;assert(OnChair(&p,&chair,seatError));p.y=0.5f;assert(!OnChair(&p,&chair,seatError));p.y=0;chair.info.chair.chairslots=1; // Furniture with event triggers must never be clicked by these scenes. s={};s.Target.id=2;s.Sequence.Start(Steps("seat",3000));chair.info.chair.triggeredEvent=1;int uses=p.session.uses; assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.session.uses==uses); // Native interaction range alone must not allow seating through a wall. chair.info.chair.triggeredEvent=0;p.los=false;s={};s.Target.id=2;s.Sequence.Start(Steps("seat",3000));uses=p.session.uses; assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.session.uses==uses);p.los=true; // Fire cast uses a normal native spell once; absence of an object times out. s={};s.Target.id=0;s.Sequence.Start(Steps("fire",30000));target=nullptr;fire=nullptr;p.casting=false; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.casts==1&&s.Casting); assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.casts==1); p.casting=false;active=true;for(unsigned i=0;i<12&&active;++i)active=AdventureSocialActions::Update(&p,s,1000,c); assert(!active&&p.casts==1); // A disappeared fire must not be recreated in a rest context that forbids creation. c.AllowCreateFire=false;s={};s.Target.id=0;s.Sequence.Start(Steps("fire",30000)); auto castCount=p.casts;assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.casts==castCount); c.AllowCreateFire=true; // Conversely, a real newly spawned object is confirmed and approached. s={};s.Target.id=0;s.Sequence.Start(Steps("fire",30000)); assert(AdventureSocialActions::Update(&p,s,1000,c));p.casting=false;fire=&chair; assert(AdventureSocialActions::Update(&p,s,1000,c)&&s.Target.id==2&&s.Approaching&&!s.Casting); // Service roles must still match while the scene is running. target=&npc;fire=nullptr;npc.service=false;p.sit=false;p.x=2;p.distance=2; s={};s.Kind="banking";s.Sequence.Start(Steps(s.Kind,15000)); assert(!AdventureSocialActions::Update(&p,s,1000,c));npc.service=true; // The visit stays active until actual departure arrival, without more cues. s={};s.Kind="banking";s.Sequence.Start(Steps(s.Kind,15000)); for(int i=0;i<60&&!s.Departing;++i) assert(AdventureSocialActions::Update(&p,s,1000,c)); assert(s.Departing&&c.departures==1);auto gestures=p.emotes.size(); travel.status.State=ClientlessTravelState::Moving; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==gestures); travel.status.State=ClientlessTravelState::Rejected;assert(!AdventureSocialActions::Update(&p,s,1000,c)); // Comparing explicitly changes to a different vendor, before departing. Unit other;other.guid.id=3;other.service=true;services={&npc,&other}; s={};s.Kind="vendor_compare";s.Sequence.Start(Steps(s.Kind,12000));travel.status.State=ClientlessTravelState::Arrived; for(int i=0;i<60&&!s.ServiceVisits;++i) assert(AdventureSocialActions::Update(&p,s,1000,c)); assert(s.ServiceVisits==1&&s.Target.id==3&&s.Approaching&&!s.Departing); target=&other; for(int i=0;i<60&&!s.Departing;++i) assert(AdventureSocialActions::Update(&p,s,1000,c)); assert(s.Departing);assert(!AdventureSocialActions::Update(&p,s,1000,c)); // No second vendor: never pretend a successful comparison or departure. target=&npc;services={&npc};s={};s.Kind="vendor_compare";s.Sequence.Start({{Cue::Listen,1}}); assert(AdventureSocialActions::Update(&p,s,1000,c)); assert(!AdventureSocialActions::Update(&p,s,1000,c)&&!s.ServiceVisits&&!s.Departing); // Investigation waits for genuine arrival, then only gestures at the object. s={};s.Kind="inspect_detail";s.Target.id=2;s.Sequence.Start(Steps(s.Kind,15000)); target=&chair;p.sit=false;p.x=2;p.distance=2;p.visible=p.los=true;chair.spawned=true; s.Approaching=true;travel.status.State=ClientlessTravelState::Moving; auto oldUses=p.session.uses,oldCasts=p.casts;auto oldEmotes=p.emotes.size(); assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==oldEmotes); travel.status.State=ClientlessTravelState::Arrived; active=true;for(int i=0;i<60&&active;++i) active=AdventureSocialActions::Update(&p,s,1000,c); assert(!active&&p.session.uses==oldUses&&p.casts==oldCasts); assert(std::find(p.emotes.begin()+oldEmotes,p.emotes.end(),EMOTE_ONESHOT_POINT)!=p.emotes.end()); s={};s.Kind="inspect";s.Target.id=2;s.Sequence.Start(Steps(s.Kind,15000));chair.spawned=false; oldEmotes=p.emotes.size();assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.emotes.size()==oldEmotes);chair.spawned=true; p.visible=false;assert(!AdventureSocialActions::Update(&p,s,1000,c));p.visible=true; // Looking around uses a fixed initial direction rather than spinning each tick. s={};s.Kind="scout";s.Target.id=0;s.Facing=1.2f;s.Sequence.Start(Steps(s.Kind,15000));p.headings.clear(); active=true;for(int i=0;i<60&&active;++i) active=AdventureSocialActions::Update(&p,s,1000,c); assert(!active&&p.headings.size()==2&&std::fabs(p.headings[0]-0.45f)<0.001&&std::fabs(p.headings[1]-1.95f)<0.001); // Profession gestures wait for arrival and never invoke use/cast handlers. c.Intent="profession";c.PrimaryProfessions={"mining","blacksmithing"}; target=&chair;p.distance=2;p.visible=p.los=true;p.states.clear(); s={};s.Kind="work_mining";s.TargetKey="2";s.Target.id=2;s.Approaching=true; s.Sequence.Start(Steps(s.Kind,20000));travel.status.State=ClientlessTravelState::Moving; oldUses=p.session.uses;oldCasts=p.casts; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.states.empty()); travel.status.State=ClientlessTravelState::Arrived; active=true;for(int i=0;i<60&&active;++i) active=AdventureSocialActions::Update(&p,s,1000,c); assert(!active&&p.session.uses==oldUses&&p.casts==oldCasts); assert(std::find(p.states.begin(),p.states.end(),EMOTE_STATE_WORK_MINING)!=p.states.end()); assert(std::find(c.TargetHistory.begin(),c.TargetHistory.end(),"work:2")!=c.TargetHistory.end()); // Losing profile authorization stops a scene before the next cue. s={};s.Kind="work_mining";s.Target.id=2;s.Sequence.Start(Steps(s.Kind,20000)); p.states.clear();c.PrimaryProfessions.clear(); assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.states.empty()); c.PrimaryProfessions={"mining"}; // Displacement while working must reapproach without advancing the cue. p.distance=8;s.Approaching=false;travel.status.State=ClientlessTravelState::Moving; assert(AdventureSocialActions::Update(&p,s,1000,c)&&s.Approaching&&p.states.empty()); // Repair requires arrival and a completed valid service scene. No packets/casts. Unit repairer;repairer.service=true;target=&repairer;c.Intent="economy"; p.distance=2;p.los=true;friendly=true;approach=true; s={};s.Kind="repair";s.Action="economy_repair";s.Target.id=1;s.Approaching=true; s.Sequence.Start(Steps(s.Kind,15000));travel.status.State=ClientlessTravelState::Moving; int beforeRepairs=p.repairs;oldCasts=p.casts;oldUses=p.session.uses; assert(AdventureSocialActions::Update(&p,s,1000,c)&&p.repairs==beforeRepairs); travel.status.State=ClientlessTravelState::Arrived; active=true;for(int i=0;i<80&&active;++i)active=AdventureSocialActions::Update(&p,s,1000,c); assert(p.repairs==beforeRepairs+1&&s.RepairDone&&p.casts==oldCasts&&p.session.uses==oldUses); assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.repairs==beforeRepairs+1); // Lost repair permission/target must never repair in the background. s={};s.Kind="repair";s.Target.id=1;s.Sequence.Start(Steps(s.Kind,15000));repairer.service=false; assert(!AdventureSocialActions::Update(&p,s,1000,c)&&p.repairs==beforeRepairs+1); } ''' with tempfile.TemporaryDirectory(prefix='vcore-social-test-') 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('Production social flow: approach before talk, coherent greeting/talk/listen/farewell, lost targets, native chair confirmation and real fire confirmation passed.')