Add AdventureBot groups, raid preparation and shared travel
Implement native bot recruitment and player admission, leader-owned destinations, intent-specific capacities, preparation scheduling and shared graph and flight travel. Support native group instance visits and separate raid entrances. Include pending personality-driven faction PvP and solo fleeing, preserving the absolute group fleeing exclusion. Add regression fixtures, migration, documentation and test-server evidence.
This commit is contained in:
1 parent
04aa87fcfc
commit
d6fea6ee68
46 files changed
+3031
-59
No files matched your search
@@ -1,5 +1,6 @@
|
||||
#include "AdventureCombatMgr.h"
|
||||
#include "AdventureInteractionMgr.h"
|
||||
#include "Group.h"
|
||||
#include "AdventureCombatPolicy.h"
|
||||
#include "AdventureBehaviorMgr.h"
|
||||
#include "ClientlessLuaEngine.h"
|
||||
@@ -7,6 +8,8 @@
|
||||
#include "ClientlessActionExecutorMgr.h"
|
||||
#include "ClientlessTravelMgr.h"
|
||||
#include "Creature.h"
|
||||
#include "DBCStructure.h"
|
||||
#include "Random.h"
|
||||
#include "Corpse.h"
|
||||
#include "Player.h"
|
||||
#include "PlayerAI.h"
|
||||
@@ -141,6 +144,7 @@ void AdventureCombatMgr::Finish(Player* p,Record& r)
|
||||
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());
|
||||
if(training && std::all_of(p->getAttackers().begin(),p->getAttackers().end(),[](Unit* u)
|
||||
{auto c=u->ToCreature();return c && c->GetScriptName()=="npc_training_dummy";}))p->CombatStop(true); // Only the bounded dummy scene may end its own combat.
|
||||
r.Fleeing=false;r.ThreatPositions.clear();
|
||||
r.AI.reset();r.Target.Clear();r.Training=false;r.Defending=false;r.UnreachableMs=0;r.Cooldown=10000;
|
||||
}
|
||||
void AdventureCombatMgr::Stop(Player* p)
|
||||
@@ -148,6 +152,42 @@ void AdventureCombatMgr::Stop(Player* p)
|
||||
auto it=_records.find(p->GetGUID().GetCounter());if(it!=_records.end())Finish(p,it->second);
|
||||
}
|
||||
void AdventureCombatMgr::Offline(Player* p){_records.erase(p->GetGUID().GetCounter());}
|
||||
Unit* AdventureCombatMgr::PvPTarget(Player* p) const
|
||||
{
|
||||
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
|
||||
if(!profile)return nullptr;
|
||||
auto trait=[&](char const* key)
|
||||
{auto it=profile->Personality.find(key);return it==profile->Personality.end()?0.0f:it->second;};
|
||||
auto chance=AdventureCombatPolicy::PvPWillingness(sAdventureBehaviorMgr->CombatIntent(p),
|
||||
trait("adventurousness"),trait("danger_tolerance"));
|
||||
if(chance<=0 || p->GetHealthPct()<AdventureCombatPolicy::MinimumHealth)return nullptr;
|
||||
if(p->GetPowerType()==POWER_MANA && p->GetMaxPower(POWER_MANA) &&
|
||||
100.0f*p->GetPower(POWER_MANA)/p->GetMaxPower(POWER_MANA)<AdventureCombatPolicy::MinimumMana)return nullptr;
|
||||
Unit* best=nullptr;
|
||||
auto consider=[&](Unit* target)
|
||||
{
|
||||
if(!Valid(p,target,false) || !p->IsHostileTo(target) ||
|
||||
!AdventureCombatPolicy::PvPLevel(p->getLevel(),target->getLevel()) ||
|
||||
p->GetExactDist(target)>AdventureCombatPolicy::PvPRadius ||
|
||||
(best && p->GetExactDist(target)>=p->GetExactDist(best)))return;
|
||||
if(auto player=target->ToPlayer())
|
||||
{if(player->GetTeam()==p->GetTeam())return;}
|
||||
else
|
||||
{
|
||||
auto creature=target->ToCreature();
|
||||
auto faction=target->GetFactionTemplateEntry();
|
||||
auto enemy=p->GetTeam()==ALLIANCE?FACTION_GROUP_MASK_HORDE:FACTION_GROUP_MASK_ALLIANCE;
|
||||
if(!creature || creature->GetControllingPlayer() || !faction || !(faction->FactionGroup & enemy) ||
|
||||
creature->isElite() || creature->isWorldBoss())return;
|
||||
}
|
||||
if(Reachable(p,target))best=target;
|
||||
};
|
||||
std::vector<Player*> players;p->GetPlayerListInGrid(players,AdventureCombatPolicy::PvPRadius);
|
||||
for(auto target:players)consider(target);
|
||||
std::vector<Creature*> creatures;p->GetCreatureListWithEntryInGrid(creatures,0,AdventureCombatPolicy::PvPRadius);
|
||||
for(auto target:creatures)consider(target);
|
||||
return best && frand(0,1)<chance?best:nullptr;
|
||||
}
|
||||
Creature* AdventureCombatMgr::Proactive(Player* p) const
|
||||
{
|
||||
auto intent=sAdventureBehaviorMgr->CombatIntent(p);
|
||||
@@ -158,6 +198,8 @@ Creature* AdventureCombatMgr::Proactive(Player* p) const
|
||||
Creature* best=nullptr;std::vector<Creature*> all;p->GetCreatureListWithEntryInGrid(all,0,AdventureCombatPolicy::SearchRadius);
|
||||
for(auto c:all)
|
||||
{
|
||||
auto faction=c->GetFactionTemplateEntry();
|
||||
if(faction && (faction->FactionGroup & (FACTION_GROUP_MASK_ALLIANCE|FACTION_GROUP_MASK_HORDE)))continue;
|
||||
if(best && p->GetExactDist(c)>=p->GetExactDist(best))continue;
|
||||
if(intent=="adventure")
|
||||
{
|
||||
@@ -174,11 +216,114 @@ Creature* AdventureCombatMgr::Proactive(Player* p) const
|
||||
}
|
||||
return best;
|
||||
}
|
||||
bool AdventureCombatMgr::Flee(Player* p,Record& r)
|
||||
{
|
||||
if(p->GetGroup())
|
||||
{
|
||||
if(r.Fleeing)
|
||||
{
|
||||
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());p->StopMoving();
|
||||
r.Fleeing=false;r.ThreatPositions.clear();
|
||||
TC_LOG_INFO("server.adventurebot","COMBAT_FLEE_CANCEL guid=%u reason='group'",p->GetGUID().GetCounter());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::vector<Unit*> threats;
|
||||
auto add=[&](Unit* u)
|
||||
{
|
||||
if(u && u!=p && u->IsAlive() && u->IsInWorld() && p->IsInPhase(u) &&
|
||||
p->CanSeeOrDetect(u) && std::find(threats.begin(),threats.end(),u)==threats.end())threats.push_back(u);
|
||||
};
|
||||
for(auto u:p->getAttackers())add(u);
|
||||
for(auto id:r.Damagers)add(ObjectAccessor::GetUnit(*p,id));
|
||||
if(r.Defending || (p->IsInCombat() && !r.Training))add(ObjectAccessor::GetUnit(*p,r.Target));
|
||||
bool danger=false;
|
||||
for(auto u:threats)
|
||||
{
|
||||
auto c=u->ToCreature();
|
||||
if(AdventureCombatPolicy::ShouldFlee(false,p->GetHealthPct(),p->getLevel(),u->getLevel(),
|
||||
c && c->isElite(),c && c->isWorldBoss()))danger=true;
|
||||
}
|
||||
if(!r.Fleeing && (!danger || r.Training))return false;
|
||||
if(!r.Fleeing)
|
||||
{
|
||||
r.Fleeing=true;r.NextEscape=0;
|
||||
sAdventureBehaviorMgr->InterruptForCombat(p);
|
||||
sClientlessLuaEngine->DismountGroundMount(p,"combat-flee");
|
||||
p->AttackStop();p->CastStop();p->StopMoving();p->GetMotionMaster()->Clear();
|
||||
TC_LOG_INFO("server.adventurebot","COMBAT_FLEE_START guid=%u",p->GetGUID().GetCounter());
|
||||
}
|
||||
// Keep last known threat positions when attackers vanish from visibility.
|
||||
if(!threats.empty())
|
||||
{
|
||||
r.ThreatPositions.clear();
|
||||
for(auto u:threats)r.ThreatPositions.push_back(u->GetPosition());
|
||||
}
|
||||
auto clearance=[&](Position const& point)
|
||||
{
|
||||
float distance=1000;
|
||||
for(auto const& threat:r.ThreatPositions)distance=std::min(distance,point.GetExactDist(threat));
|
||||
return distance;
|
||||
};
|
||||
if(!p->IsInCombat() && clearance(p->GetPosition())>=50)
|
||||
{
|
||||
r.Fleeing=false;r.ThreatPositions.clear();r.Damagers.clear();
|
||||
Finish(p,r);r.Cooldown=30000;
|
||||
TC_LOG_INFO("server.adventurebot","COMBAT_FLEE_END guid=%u",p->GetGUID().GetCounter());
|
||||
return true;
|
||||
}
|
||||
auto now=GameTime::GetGameTime();
|
||||
if(now<r.NextEscape)return true;
|
||||
r.NextEscape=now+2;
|
||||
std::vector<Creature*> creatures;p->GetCreatureListWithEntryInGrid(creatures,0,80);
|
||||
std::vector<Player*> players;p->GetPlayerListInGrid(players,80);
|
||||
std::vector<Unit*> nearby;
|
||||
for(auto c:creatures)nearby.push_back(c);
|
||||
for(auto other:players)nearby.push_back(other);
|
||||
Position best;bool found=false;float score=-1e30f;
|
||||
float current=clearance(p->GetPosition());
|
||||
for(float radius:{18.0f,30.0f})for(unsigned i=0;i<12;++i)
|
||||
{
|
||||
float angle=i*6.2831853f/12;
|
||||
float x=p->GetPositionX()+radius*std::cos(angle),y=p->GetPositionY()+radius*std::sin(angle);
|
||||
float z=p->GetMap()->GetHeight(p->GetPhaseShift(),x,y,p->GetPositionZ()+5,true,15);
|
||||
if(z<=INVALID_HEIGHT)continue;
|
||||
Position end;end.Relocate(x,y,z);
|
||||
float distance=clearance(end);
|
||||
if(distance<current+3)continue;
|
||||
bool unsafe=false;
|
||||
for(auto u:nearby)
|
||||
{
|
||||
if(!u->IsAlive() || !p->IsInPhase(u) || !p->CanSeeOrDetect(u) || !p->IsHostileTo(u) ||
|
||||
std::find(threats.begin(),threats.end(),u)!=threats.end())continue;
|
||||
if(AdventureCombatPolicy::SegmentDistance(u->GetPositionX(),u->GetPositionY(),u->GetPositionZ(),
|
||||
p->GetPositionX(),p->GetPositionY(),p->GetPositionZ(),x,y,z)<12){unsafe=true;break;}
|
||||
}
|
||||
if(unsafe)continue;
|
||||
ClientlessPathDiagnostics path;
|
||||
if(!sClientlessTravelMgr->AnalyzePath(p,end,path) || !path.Complete || path.Suspicious || path.PathLength>60)continue;
|
||||
float value=distance-0.15f*path.PathLength;
|
||||
if(value>score){score=value;best=end;found=true;}
|
||||
}
|
||||
sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());
|
||||
p->AttackStop();p->CastStop();
|
||||
if(found && sClientlessTravelMgr->StartTravel(p,best))
|
||||
TC_LOG_INFO("server.adventurebot","COMBAT_FLEE_PATH guid=%u clearance=%.2f",p->GetGUID().GetCounter(),clearance(best));
|
||||
else
|
||||
{
|
||||
p->StopMoving();
|
||||
auto target=ObjectAccessor::GetUnit(*p,r.Target);
|
||||
if(r.AI && Valid(p,target,false) && p->IsWithinMeleeRange(target))r.AI->Tick(target,500);
|
||||
TC_LOG_INFO("server.adventurebot","COMBAT_FLEE_BLOCKED guid=%u",p->GetGUID().GetCounter());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool AdventureCombatMgr::Death(Player* p,Record& r)
|
||||
{
|
||||
if(!r.Dead)
|
||||
{
|
||||
if(r.AI)Finish(p,r);
|
||||
r.Fleeing=false;r.ThreatPositions.clear();
|
||||
sAdventureBehaviorMgr->InterruptForCombat(p);r.Damagers.clear();r.Dead=true;r.Suspended=true;
|
||||
TC_LOG_INFO("server.adventurebot","DEATH_RELEASE guid=%u",p->GetGUID().GetCounter());
|
||||
}
|
||||
@@ -250,8 +395,8 @@ bool AdventureCombatMgr::Update(Player* p,uint32 diff)
|
||||
if(!p->IsInWorld())return false;
|
||||
auto& r=_records[p->GetGUID().GetCounter()];
|
||||
r.Cooldown=r.Cooldown>diff?r.Cooldown-diff:0;
|
||||
if(p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed())return r.AI || !p->IsAlive();
|
||||
if(r.Timer>diff){r.Timer-=diff;return r.AI || !p->IsAlive() || r.Dead;}
|
||||
if(p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed())return r.Fleeing || r.AI || !p->IsAlive();
|
||||
if(r.Timer>diff){r.Timer-=diff;return r.Fleeing || r.AI || !p->IsAlive() || r.Dead;}
|
||||
r.Timer=500;
|
||||
if(!p->IsAlive())return Death(p,r);
|
||||
if(r.Dead){r.Dead=false;r.Suspended=false;r.NextCorpseSearch=0;r.FailedCorpsePoints.clear();r.Cooldown=30000;r.Timer=0;return false;}
|
||||
@@ -264,8 +409,21 @@ bool AdventureCombatMgr::Update(Player* p,uint32 diff)
|
||||
if(r.AI)Finish(p,r);
|
||||
Start(p,r,c,false,true,0);break;
|
||||
}
|
||||
if(!r.AI && !p->GetGroup() && !p->IsInCombat() && !r.Cooldown)
|
||||
if(auto c=Proactive(p))Start(p,r,c,false,false,0);
|
||||
if(Flee(p,r))return true;
|
||||
if(!r.AI && (!p->GetGroup() || p->GetGroup()->GetLeaderGUID()==p->GetGUID()) && !p->IsInCombat() && !r.Cooldown)
|
||||
{
|
||||
auto now=GameTime::GetGameTime();
|
||||
if(now>=r.NextPvP)
|
||||
{
|
||||
r.NextPvP=now+30;
|
||||
if(auto target=PvPTarget(p))
|
||||
{
|
||||
TC_LOG_INFO("server.adventurebot","COMBAT_PVP_INITIATE guid=%u target='%s'",p->GetGUID().GetCounter(),target->GetGUID().ToString().c_str());
|
||||
Start(p,r,target,false,false,0);
|
||||
}
|
||||
}
|
||||
if(!r.AI && !p->GetGroup())if(auto c=Proactive(p))Start(p,r,c,false,false,0);
|
||||
}
|
||||
if(!r.AI)return false;
|
||||
auto c=ObjectAccessor::GetUnit(*p,r.Target);
|
||||
if(!Valid(p,c,r.Training) || (r.Until && GameTime::GetGameTime()>=r.Until) || p->GetExactDist(c)>80)
|
||||
@@ -295,7 +453,7 @@ bool AdventureCombatMgr::Update(Player* p,uint32 diff)
|
||||
bool AdventureCombatMgr::Busy(Player* player) const
|
||||
{
|
||||
auto it=_records.find(player->GetGUID().GetCounter());
|
||||
return it!=_records.end() && (it->second.AI || it->second.Dead || !it->second.Damagers.empty());
|
||||
return it!=_records.end() && (it->second.Fleeing || it->second.AI || it->second.Dead || !it->second.Damagers.empty());
|
||||
}
|
||||
|
||||
bool AdventureCombatMgr::Assist(Player* p,Unit* target)
|
||||
|
||||
Reference in new issue
Block a user