Add AdventureBot social interactions and player-led groups

This commit is contained in:
Nico committed 2026-10-28 01:11:54 +01:00
1 parent 39a515dbd1
commit e333f2f9fc
29 files changed
+2003 -29

No files matched your search

@@ -33,9 +33,14 @@ struct Position{
float GetExactDist(Position const* b)const{return GetExactDist(*b);}
};
struct Map{float GetHeight(int,float,float,float,bool,float){return 0;}}map;
struct Creature;
struct Unit:Position{virtual Creature* ToCreature(){return nullptr;}};
struct Creature:Unit{ObjectGuid guid;bool valid=true,alive=true,controlled=false,evading=false,boss=false,phase=true,visible=true,inWorld=true;unsigned level=60;std::string script="mob";Creature(unsigned n):guid{n}{}ObjectGuid GetGUID(){return guid;}Creature* ToCreature()override{return this;}bool GetControllingPlayer(){return controlled;}bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}bool IsEvadingAttacks(){return evading;}bool isWorldBoss(){return boss;}unsigned getLevel(){return level;}std::string GetScriptName(){return script;}};
struct Creature;struct Player;
struct Unit:Position{
ObjectGuid guid;bool valid=true,alive=true,phase=true,visible=true,inWorld=true;
virtual Creature* ToCreature(){return nullptr;}virtual Player* ToPlayer(){return nullptr;}
virtual bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}virtual ObjectGuid GetGUID(){return guid;}
void GetNearPoint(Player*,float&a,float&b,float&c,float,float){a=x;b=y;c=z;}float GetAngle(Player*){return 0;}
};
struct Creature:Unit{bool controlled=false,evading=false,boss=false;unsigned level=60;std::string script="mob";Creature(unsigned n){guid={n};}ObjectGuid GetGUID(){return guid;}Creature* ToCreature()override{return this;}bool GetControllingPlayer(){return controlled;}bool IsAlive(){return alive;}bool IsInWorld(){return inWorld;}bool IsEvadingAttacks(){return evading;}bool isWorldBoss(){return boss;}unsigned getLevel(){return level;}std::string GetScriptName(){return script;}};
struct Player;
struct Corpse:Position{
time_t released=100;int saves=0;ObjectGuid GetGUID(){return {700};}time_t GetGhostTime(){return released;}int GetType(){return 0;}
@@ -45,10 +50,10 @@ struct WorldPacket{int opcode;WorldPacket(int o,int):opcode(o){}template<class T
struct Session{Player* owner=nullptr;int releases=0,reclaims=0;void HandleRepopRequestOpcode(WorldPacket&);void HandleReclaimCorpseOpcode(WorldPacket&);};
struct Player:Unit{
bool alive=true,ghost=false,teleport=false,combat=false,melee=true,los=true;unsigned delay=30;int saves=0,revives=0,bones=0,graveyards=0;bool sickness=false;int durability=75;
Session session;Player(){session.owner=this;}ObjectGuid GetGUID(){return {26};}bool IsAlive(){return alive;}bool IsInWorld(){return true;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return false;}bool IsInFlight(){return false;}bool IsCharmed(){return false;}
Session session;Player(){session.owner=this;guid={26};}ObjectGuid GetGUID(){return guid;}Player* ToPlayer()override{return this;}bool GetGroup(){return false;}bool IsAlive(){return alive;}bool IsInWorld(){return true;}bool IsInCombat(){return combat;}bool IsBeingTeleported(){return teleport;}bool GetTransport(){return false;}bool IsInFlight(){return false;}bool IsCharmed(){return false;}
unsigned GetMapId(){return 0;}unsigned GetInstanceId(){return 0;}bool InBattleground(){return false;}bool InArena(){return false;}bool HasFlag(int,int){return ghost;}Session*GetSession(){return &session;}Corpse*GetCorpse(){return &corpse;}
uint32 GetCorpseReclaimDelay(bool){return delay;}Map*GetMap(){return &map;}int GetPhaseShift(){return 0;}
bool IsInPhase(Creature*c){return c->phase;}bool CanSeeOrDetect(Creature*c){return c->visible;}bool IsValidAttackTarget(Creature*c){return c->valid;}unsigned getLevel(){return 60;}bool IsWithinMeleeRange(Creature*){return melee;}float GetCombatReach(){return 1;}float GetAngle(Player*){return 0;}
bool IsInPhase(Unit*c){return c->phase;}bool CanSeeOrDetect(Unit*c){return c->visible;}bool IsValidAttackTarget(Unit*c){return c->valid;}unsigned getLevel(){return 60;}bool IsWithinMeleeRange(Unit*){return melee;}float GetCombatReach(){return 1;}float GetAngle(Player*){return 0;}
void SaveToDB(){++saves;}void RepopAtGraveyard(){++graveyards;teleport=true;Relocate(-100,0,0);}void ResurrectPlayer(float hp,bool sick){assert(hp==0.5f);sickness=sick;alive=true;ghost=false;++revives;}void SpawnCorpseBones(){++bones;}
void StopMoving(){}
};
@@ -59,7 +64,7 @@ void Session::HandleReclaimCorpseOpcode(WorldPacket&){++reclaims;assert(corpse.I
void nearPoint(Creature*,Player*,float&,float&,float&,float,float){}
struct FakeCreature:Creature{using Creature::Creature;void GetNearPoint(Player*,float&x,float&y,float&z,float,float){x=this->x;y=this->y;z=this->z;}float GetAngle(Player*){return 0;}};
namespace GameTime{time_t now=100;time_t GetGameTime(){return now;}}
namespace ObjectAccessor{std::unordered_map<unsigned,Creature*> objects;Creature* GetCreature(Player&,ObjectGuid g){auto i=objects.find(g.id);return i==objects.end()?nullptr:i->second;}}
namespace ObjectAccessor{std::unordered_map<unsigned,Unit*> objects;Unit* GetUnit(Player&,ObjectGuid g){auto i=objects.find(g.id);return i==objects.end()?nullptr:i->second;}}
struct ClientlessPathDiagnostics{bool Complete=true,Suspicious=false;float PathLength=100;};
enum class ClientlessTravelState{Moving,Arrived,Stuck,PathRejected};
struct TravelStatus{ClientlessTravelState State=ClientlessTravelState::Arrived;Position Target;};
@@ -78,11 +83,11 @@ code=code.replace('bool GetControllingPlayer(){return controlled;}bool IsAlive()
# Forward declaration needed in Creature's adapter.
code=code.replace('struct Creature;','struct Creature;struct Player;',1)
code+=header+r'''
class AdventureCombatMgr::Fighter{public:int ticks=0;void Tick(Creature*,uint32){++ticks;}};
class AdventureCombatMgr::Fighter{public:int ticks=0;void Tick(Unit*,uint32){++ticks;}};
bool AdventureCombatMgr::Reachable(Player*p,Creature*)const{return p->los;}
bool AdventureCombatMgr::Reachable(Player*p,Unit*)const{return p->los;}
Creature* AdventureCombatMgr::Proactive(Player*)const{return nullptr;}
bool AdventureCombatMgr::Start(Player*,Record&r,Creature*c,bool train,bool defense,time_t until){r.AI=std::make_shared<Fighter>();r.Target=c->guid;r.Training=train;r.Defending=defense;r.Until=until;return true;}
bool AdventureCombatMgr::Start(Player*,Record&r,Unit*c,bool train,bool defense,time_t until){r.AI=std::make_shared<Fighter>();r.Target=c->GetGUID();r.Training=train;r.Defending=defense;r.Until=until;return true;}
void AdventureCombatMgr::Finish(Player*,Record&r){r.AI.reset();r.Target.Clear();r.Defending=false;r.Training=false;r.Cooldown=10000;}
'''
for name in ('bool AdventureCombatMgr::Valid(', 'void AdventureCombatMgr::DamageTaken(', 'bool AdventureCombatMgr::Death(', 'bool AdventureCombatMgr::Update('):code+='\n'+function(name)
@@ -101,6 +106,9 @@ int main(){
assert(SegmentDistance(5,3,0,0,0,0,10,0,0)==3);
AdventureCombatMgr mgr;Player p;Creature a(1),b(2),c(3);ObjectAccessor::objects={{1,&a},{2,&b},{3,&c}};
Player enemy;enemy.guid={99};ObjectAccessor::objects[99]=&enemy;
assert(mgr.Valid(&p,&enemy,false));assert(!mgr.Valid(&p,&enemy,true));
enemy.valid=false;assert(!mgr.Valid(&p,&enemy,false));enemy.valid=true;
assert(mgr.Valid(&p,&a,false));assert(!mgr.Valid(&p,&a,true));
Creature dummy(4);dummy.script="npc_training_dummy";assert(mgr.Valid(&p,&dummy,true));assert(!mgr.Valid(&p,&dummy,false));
dummy.level=63;assert(!mgr.Valid(&p,&dummy,true));dummy.level=60;
@@ -110,6 +118,7 @@ int main(){
dummy.controlled=true;assert(!mgr.Valid(&p,&dummy,true));dummy.controlled=false;
dummy.valid=false;assert(!mgr.Valid(&p,&dummy,true));
auto&r=mgr._records[26];
mgr.DamageTaken(&p,&enemy,1);assert(r.Damagers.front().id==99);r.Damagers.clear();
mgr.DamageTaken(&p,&a,0);assert(r.Damagers.empty());
mgr.DamageTaken(&p,&a,5);mgr.DamageTaken(&p,&b,5);mgr.Update(&p,500);
assert(r.Target.id==1 && r.Defending); // first actual damager
@@ -117,6 +126,10 @@ int main(){
a.alive=false;mgr.Update(&p,500);assert(!r.AI);mgr.Update(&p,500);assert(r.Target.id==2);
mgr.Finish(&p,r);r.Damagers.clear();mgr.Start(&p,r,&c,true,false,500);
mgr.DamageTaken(&p,&b,1);mgr.Update(&p,500);assert(r.Target.id==2 && r.Defending && !r.Training);
// Actual player attackers use the same first-damager focus and native PvP permission.
mgr.Finish(&p,r);r.Damagers.clear();mgr.DamageTaken(&p,&enemy,1);mgr.Update(&p,500);
assert(r.Target.id==99 && r.Defending);mgr.DamageTaken(&p,&c,1);mgr.Update(&p,500);assert(r.Target.id==99);
enemy.valid=false;mgr.Update(&p,500);assert(!r.AI);r.Damagers.clear();
// Release through native handler and save ghost plus corpse timestamp.
mgr.Finish(&p,r);p.alive=false;p.ghost=false;r.Dead=false;mgr.Death(&p,r);
assert(p.ghost && p.session.releases==1 && p.saves==1 && corpse.saves==1);