Files
Viano-Core/tests/clientless/test_clientless_companion.py
T

114 lines
9.2 KiB
Python

"""Execute the production native companion/item adapter against deterministic core doubles."""
from pathlib import Path
import subprocess,tempfile
root=Path(__file__).resolve().parents[2]
source=(root/'src/server/game/World/ClientlessCompanionMgr.cpp').read_text()
header=(root/'src/server/game/World/ClientlessCompanionMgr.h').read_text()
header='\n'.join(line for line in header.splitlines() if not line.startswith('#include'))
code=r'''
#include <cassert>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#include <optional>
#include <unordered_map>
using uint32=uint32_t;using uint8=uint8_t;
#define TC_GAME_API
#define TC_LOG_INFO(...) ((void)0)
#define TC_LOG_WARN(...) ((void)0)
struct ObjectGuid {int id=0;bool IsEmpty()const{return !id;}bool operator==(ObjectGuid b)const{return id==b.id;}bool operator!=(ObjectGuid b)const{return id!=b.id;}std::string ToString()const{return std::to_string(id);}uint32 GetCounter()const{return id;}};
constexpr float DEFAULT_FOLLOW_ANGLE=1.0f;
constexpr uint32 SPELL_EFFECT_SUMMON=28,UNIT_CREATED_BY_SPELL=1,ITEM_SPELLTRIGGER_ON_USE=0,TARGET_FLAG_UNIT_MINIPET=0x10000,SPELL_CAST_OK=0;
enum class SummonTitle{Pet,Companion};
struct Properties{int Title=1;}props;
struct PropertyStore{Properties const* LookupEntry(uint32 id){return id==41?&props:nullptr;}}propertyStore;auto& sSummonPropertiesStore=propertyStore;
struct SpellInfo {uint32 Id=10673;bool channel=false,moveAllowed=false;struct Effect{uint32 Effect=28,MiscValueB=41;};std::vector<Effect> Effects{{}};bool IsChanneled()const{return channel;}bool IsMoveAllowedChannel()const{return moveAllowed;}};
struct Spells{SpellInfo cat,leash;Spells(){leash.Id=48722;leash.Effects.clear();}SpellInfo const* GetSpellInfo(uint32 id){return id==10673?&cat:id==48722?&leash:nullptr;}}spells;auto sSpellMgr=&spells;
struct ItemTemplate{struct Effect{int Trigger=0,SpellID=48722;};std::vector<Effect> Effects{{}};}itemTemplate;
struct Item{ItemTemplate const* GetTemplate(){return &itemTemplate;}uint8 GetBagSlot(){return 255;}uint8 GetSlot(){return 24;}ObjectGuid GetGUID(){return {7};}}item;
struct Objects{ItemTemplate const* GetItemTemplate(uint32 id){return id==37460?&itemTemplate:nullptr;}}objects;auto sObjectMgr=&objects;
namespace WorldPackets::Spells {struct SpellCastRequest{int SpellID=0;struct {uint32 Flags=0;std::optional<ObjectGuid> Unit;}Target;};}
struct SpellCastRequestItemData{uint8 bag,slot;ObjectGuid guid;SpellCastRequestItemData(uint8 b,uint8 s,ObjectGuid g):bag(b),slot(s),guid(g){}};
struct PendingSpellCastRequest{WorldPackets::Spells::SpellCastRequest request;SpellCastRequestItemData item;PendingSpellCastRequest(WorldPackets::Spells::SpellCastRequest&& r,SpellCastRequestItemData i):request(r),item(i){}};
struct Creature{
struct Motion{int follows=0;template<class T>void MoveFollow(T*,float distance,float,bool formation,bool catchUp,bool face){assert(distance==1.5f&&!formation&&catchUp&&face);++follows;}}motion;
Motion* GetMotionMaster(){return &motion;}
ObjectGuid guid{2},owner{26};uint32 created=10673;bool alive=true,aura=false,summon=true;int dismissals=0,removals=0;
bool IsAlive(){return alive;}ObjectGuid GetOwnerGUID(){return owner;}ObjectGuid GetGUID(){return guid;}uint32 GetUInt32Value(uint32){return created;}uint32 GetEntry(){return 7385;}
bool HasAura(uint32 id,ObjectGuid caster){return aura&&id==48722&&caster.id==26;}
void RemoveAurasDueToSpell(uint32 id,ObjectGuid caster){assert(id==48722&&caster.id==26);aura=false;++removals;}
bool IsSummon(){return summon;}Creature* ToTempSummon(){return this;}void UnSummon(){++dismissals;alive=false;}
};
struct History{bool cooldown=false;bool HasGlobalCooldown(SpellInfo const*){return cooldown;}};
struct Player{
bool learned=false,alive=true,combat=false,transport=false,taxi=false,flying=false,teleport=false,hasItem=false,casting=false,requestAllowed=true,applyLeash=true;
Creature* pet=nullptr;int casts=0,learns=0,adds=0,requests=0,interrupts=0;History history;
float GetExactDist(Creature*){return 2;}bool IsWithinLOSInMap(Creature*){return true;}
ObjectGuid GetGUID(){return {26};}bool IsInWorld(){return true;}bool IsAlive(){return alive;}bool IsInCombat(){return combat;}bool GetTransport(){return transport;}bool IsInFlight(){return taxi;}bool IsFlying(){return flying;}bool IsBeingTeleported(){return teleport;}
bool HasSpell(uint32 id){return id==10673&&learned;}void LearnSpell(uint32 id,bool){assert(id==10673);learned=true;++learns;}
Item* GetItemByEntry(uint32 id){return hasItem&&id==37460?&item:nullptr;}bool AddItem(uint32 id,int count){assert(id==37460&&count==1);++adds;hasItem=true;return true;}
ObjectGuid GetCritterGUID(){return pet?pet->guid:ObjectGuid{};}
int CastSpell(Player*,uint32 id,bool triggered){assert(id==10673&&!triggered);++casts;return SPELL_CAST_OK;}
bool CanRequestSpellCast(SpellInfo const*){return requestAllowed;}History* GetSpellHistory(){return &history;}bool IsNonMeleeSpellCast(bool){return casting;}
void RequestSpellCast(std::unique_ptr<PendingSpellCastRequest> r,SpellInfo const* s){
assert(s->Id==48722&&r->request.SpellID==48722&&r->request.Target.Flags==TARGET_FLAG_UNIT_MINIPET);
assert(r->request.Target.Unit->id==2&&r->item.guid.id==7&&r->item.bag==255&&r->item.slot==24);
++requests;if(pet&&applyLeash)pet->aura=true;
}
void InterruptNonMeleeSpells(bool,uint32 id){assert(id==10673||id==48722);++interrupts;}
};
namespace ObjectAccessor{Creature* GetCreature(Player& p,ObjectGuid guid){return p.pet&&p.pet->guid==guid?p.pet:nullptr;}}
'''
code+=header+'\n'+source[source.index('namespace\n'):]+r'''
int main(){
Player p;ClientlessCompanionScene s;Creature cat;
assert(!ClientlessCompanionMgr::Prepare(&p,123,37460));assert(p.learns==0&&p.adds==0);
assert(ClientlessCompanionMgr::Prepare(&p,10673,37460));assert(p.learns==1&&p.adds==1);
assert(ClientlessCompanionMgr::Prepare(&p,10673,37460));assert(p.learns==1&&p.adds==1);
p.combat=true;assert(!ClientlessCompanionMgr::Start(&p,s,10673,37460));p.combat=false;
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460)&&p.casts==1&&s.Summoned);
for(int i=0;i<4;++i)assert(ClientlessCompanionMgr::Update(&p,s,1000)&&!s.Ready);
p.pet=&cat;assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Companion.id==2&&p.requests==1&&!s.Ready&&cat.motion.follows==1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&cat.aura);
ClientlessCompanionMgr::Reset(&p,s);assert(cat.dismissals==1&&cat.removals==1&&!s.Active);
// Reuse a matching pre-existing pet and leash without taking ownership of either.
cat.alive=true;cat.aura=true;p.pet=&cat;
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460)&&!s.Summoned);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&!s.OwnLeash);
ClientlessCompanionMgr::Reset(&p,s);assert(cat.dismissals==1&&cat.removals==1&&cat.aura&&cat.motion.follows==1);
// Another owner's or another spell's companion must not be replaced or used.
cat.owner.id=99;assert(!ClientlessCompanionMgr::Start(&p,s,10673,37460));cat.owner.id=26;
cat.created=999;assert(!ClientlessCompanionMgr::Start(&p,s,10673,37460));cat.created=10673;
// Missing optional item does not block a real pet walk.
p.hasItem=false;cat.aura=false;assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready);ClientlessCompanionMgr::Reset(&p,s);
// The native item request is deferred until it can execute immediately.
p.hasItem=true;p.history.cooldown=true;assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
auto requests=p.requests;assert(ClientlessCompanionMgr::Update(&p,s,1000)&&!s.Ready&&p.requests==requests);
p.history.cooldown=false;p.applyLeash=false;assert(ClientlessCompanionMgr::Update(&p,s,1000));
for(int i=0;i<8;++i)assert(ClientlessCompanionMgr::Update(&p,s,1000));assert(s.Ready&&!cat.aura);
p.pet=nullptr;assert(!ClientlessCompanionMgr::Update(&p,s,1000));ClientlessCompanionMgr::Reset(&p,s);
// No fabricated companion after an unsuccessful normal summon cast.
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
bool active=true;for(int i=0;i<10&&active;++i)active=ClientlessCompanionMgr::Update(&p,s,1000);
assert(!active&&!s.Ready);ClientlessCompanionMgr::Reset(&p,s);
// Immobile channels are skipped; native move-allowed leash channels are used.
p.pet=&cat;spells.leash.channel=true;assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
requests=p.requests;assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&p.requests==requests);
ClientlessCompanionMgr::Reset(&p,s);spells.leash.moveAllowed=true;p.applyLeash=true;
assert(ClientlessCompanionMgr::Start(&p,s,10673,37460));
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&p.requests==requests+1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&cat.aura);
cat.aura=false;requests=p.requests;
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&!s.Ready&&s.LeashRetries==1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&p.requests==requests+1);
assert(ClientlessCompanionMgr::Update(&p,s,1000)&&s.Ready&&s.Leashed);
}
'''
with tempfile.TemporaryDirectory(prefix='vcore-companion-') as folder:
cpp=Path(folder)/'test.cpp';binary=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('Native companion: provisioning, delayed summon, ownership, real item target, leash confirmation/timeout, reuse and cleanup passed.')