Preserve instance groups during leader absence and travel
This commit is contained in:
1 parent
65e4abb0b9
commit
f8ee1d0ff6
5 files changed
+90
-3
No files matched your search
@@ -2217,6 +2217,33 @@ bool AdventureBehaviorMgr::ProtectGroupLeave(Player* player) const
|
|||||||
r.State==Phase::Handover;
|
r.State==Phase::Handover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AdventureBehaviorMgr::ProtectInstanceGroupLeave(Player* player) const
|
||||||
|
{
|
||||||
|
auto group=player->GetGroup();
|
||||||
|
if(!group)return false;
|
||||||
|
auto now=GameTime::GetGameTime();
|
||||||
|
for(auto reference=group->GetFirstMember();reference;reference=reference->next())
|
||||||
|
{
|
||||||
|
auto member=reference->GetSource();
|
||||||
|
if(!member || !member->IsInWorld())continue;
|
||||||
|
// The leader can already be waiting inside while late members travel.
|
||||||
|
// This also covers human leaders without an AdventureBot record.
|
||||||
|
if(member->GetMap()->IsDungeon())return true;
|
||||||
|
auto it=_records.find(member->GetGUID().GetCounter());
|
||||||
|
if(it==_records.end())continue;
|
||||||
|
auto const& r=it->second;
|
||||||
|
if(r.Instance.Active)
|
||||||
|
{
|
||||||
|
if(r.Instance.Stage!=AdventureInstancePolicy::Phase::Rest)return true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if((r.Intent=="raid" || r.Intent=="instance") && r.Deadline>now)
|
||||||
|
if(auto location=sAdventureLocationMgr->Find(r.Location))
|
||||||
|
if(location->InstanceMap)return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void AdventureBehaviorMgr::InterruptForInteraction(Player* player)
|
void AdventureBehaviorMgr::InterruptForInteraction(Player* player)
|
||||||
{
|
{
|
||||||
InterruptForCombat(player);
|
InterruptForCombat(player);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ public:
|
|||||||
std::string InteractionIntent(Player* player) const;
|
std::string InteractionIntent(Player* player) const;
|
||||||
std::string InteractionSchedule(Player* player);
|
std::string InteractionSchedule(Player* player);
|
||||||
bool ProtectGroupLeave(Player* player) const;
|
bool ProtectGroupLeave(Player* player) const;
|
||||||
|
bool ProtectInstanceGroupLeave(Player* player) const;
|
||||||
private:
|
private:
|
||||||
bool EditPlan(ChatHandler* handler, std::string const& args);
|
bool EditPlan(ChatHandler* handler, std::string const& args);
|
||||||
enum class Phase {Idle, Travel, OnSite, Switching, Handover, WaitingGroup};
|
enum class Phase {Idle, Travel, OnSite, Switching, Handover, WaitingGroup};
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ bool AdventureInteractionMgr::Update(Player* p,uint32 diff)
|
|||||||
if(protectedFormation){r.LastActivity=now;r.AfkSince=0;r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);}
|
if(protectedFormation){r.LastActivity=now;r.AfkSince=0;r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);}
|
||||||
bool protectedTravel=sAdventureGroupTravelMgr->Protected(p) || p->isUsingLfg() || p->InBattlegroundQueue() || p->InBattleground();
|
bool protectedTravel=sAdventureGroupTravelMgr->Protected(p) || p->isUsingLfg() || p->InBattlegroundQueue() || p->InBattleground();
|
||||||
if(protectedTravel){r.LastActivity=now;r.AfkSince=0;}
|
if(protectedTravel){r.LastActivity=now;r.AfkSince=0;}
|
||||||
|
bool protectedInstance=sAdventureBehaviorMgr->ProtectInstanceGroupLeave(p);
|
||||||
|
if(protectedInstance){r.LastActivity=now;r.AfkSince=0;}
|
||||||
if(!protectedFormation && !protectedTravel && AdventureInteractionPolicy::CanLeave(p->IsAlive(),p->IsInCombat(),p->GetMap()->IsDungeon(),p->GetTransport() || p->IsInFlight() || p->IsFlying()))
|
if(!protectedFormation && !protectedTravel && AdventureInteractionPolicy::CanLeave(p->IsAlive(),p->IsInCombat(),p->GetMap()->IsDungeon(),p->GetTransport() || p->IsInFlight() || p->IsFlying()))
|
||||||
{
|
{
|
||||||
if(r.Schedule!=sAdventureBehaviorMgr->InteractionSchedule(p))
|
if(r.Schedule!=sAdventureBehaviorMgr->InteractionSchedule(p))
|
||||||
@@ -186,8 +188,8 @@ bool AdventureInteractionMgr::Update(Player* p,uint32 diff)
|
|||||||
r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);
|
r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);
|
||||||
if(!sAdventureGroupMgr->PlannedTransition(p) && frand(0,1)>=Loyalty(p)){Leave(p,r,"intent-change");return false;}
|
if(!sAdventureGroupMgr->PlannedTransition(p) && frand(0,1)>=Loyalty(p)){Leave(p,r,"intent-change");return false;}
|
||||||
}
|
}
|
||||||
if(sAdventureGroupTravelMgr->Separated(p)){Leave(p,r,"leader-distance");return false;}
|
if(!protectedInstance && sAdventureGroupTravelMgr->Separated(p)){Leave(p,r,"leader-distance");return false;}
|
||||||
if(AdventureInteractionPolicy::LeaderExpired(now,r.AfkSince?r.AfkSince:r.LastActivity)){Leave(p,r,"leader-afk");return false;}
|
if(!protectedInstance && AdventureInteractionPolicy::LeaderExpired(now,r.AfkSince?r.AfkSince:r.LastActivity)){Leave(p,r,"leader-afk");return false;}
|
||||||
}
|
}
|
||||||
// Existing warrior executor supplies damage assistance. Roles/healing
|
// Existing warrior executor supplies damage assistance. Roles/healing
|
||||||
// deliberately remain a later profile-driven extension.
|
// deliberately remain a later profile-driven extension.
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ unsigned urand(unsigned a,unsigned){return a;}float roll=0;float frand(float,flo
|
|||||||
namespace ObjectAccessor {std::unordered_map<ObjectGuid,Player*> players;Player*FindPlayer(ObjectGuid id){auto it=players.find(id);return it==players.end()?nullptr:it->second;}}
|
namespace ObjectAccessor {std::unordered_map<ObjectGuid,Player*> players;Player*FindPlayer(ObjectGuid id){auto it=players.find(id);return it==players.end()?nullptr:it->second;}}
|
||||||
struct Profile {std::string Locale="deDE";std::unordered_map<std::string,float> Personality;};
|
struct Profile {std::string Locale="deDE";std::unordered_map<std::string,float> Personality;};
|
||||||
struct Lua {std::unordered_map<unsigned,Profile> profiles;Profile const*GetAdventureProfile(unsigned id){auto it=profiles.find(id);return it==profiles.end()?nullptr:&it->second;}}lua;auto sClientlessLuaEngine=&lua;
|
struct Lua {std::unordered_map<unsigned,Profile> profiles;Profile const*GetAdventureProfile(unsigned id){auto it=profiles.find(id);return it==profiles.end()?nullptr:&it->second;}}lua;auto sClientlessLuaEngine=&lua;
|
||||||
struct Behavior {int interrupts=0;bool protectGroupLeave=false;std::string intent="social",schedule="social:inn";std::string InteractionIntent(Player*){return intent;}std::string InteractionSchedule(Player*){return schedule;}bool ProtectGroupLeave(Player*)const{return protectGroupLeave;}void ResumePersonalPlan(Player*){}void InterruptForInteraction(Player*){++interrupts;}}behavior;auto sAdventureBehaviorMgr=&behavior;
|
struct Behavior {int interrupts=0;bool protectGroupLeave=false,protectInstance=false;std::string intent="social",schedule="social:inn";std::string InteractionIntent(Player*){return intent;}std::string InteractionSchedule(Player*){return schedule;}bool ProtectGroupLeave(Player*)const{return protectGroupLeave;}bool ProtectInstanceGroupLeave(Player*)const{return protectInstance;}void ResumePersonalPlan(Player*){}void InterruptForInteraction(Player*){++interrupts;}}behavior;auto sAdventureBehaviorMgr=&behavior;
|
||||||
struct Combat {bool busy=false;int assists=0;bool Busy(Player*){return busy;}bool Assist(Player*,Unit*){++assists;return true;}}combat;auto sAdventureCombatMgr=&combat;
|
struct Combat {bool busy=false;int assists=0;bool Busy(Player*){return busy;}bool Assist(Player*,Unit*){++assists;return true;}}combat;auto sAdventureCombatMgr=&combat;
|
||||||
struct ClientlessAction{std::string Name;std::unordered_map<std::string,std::string> Parameters;};
|
struct ClientlessAction{std::string Name;std::unordered_map<std::string,std::string> Parameters;};
|
||||||
struct Core {time_t activity=0;int queued=0;time_t LastActivity(ObjectGuid){return activity;}bool QueueAction(unsigned,ClientlessAction){++queued;return true;}}core;auto sClientlessPlayerMgr=&core;
|
struct Core {time_t activity=0;int queued=0;time_t LastActivity(ObjectGuid){return activity;}bool QueueAction(unsigned,ClientlessAction){++queued;return true;}}core;auto sClientlessPlayerMgr=&core;
|
||||||
@@ -133,6 +133,17 @@ int main(){
|
|||||||
mgr._records.clear();behavior.protectGroupLeave=true;behavior.schedule="groupformation:meeting";leader.afk=true;
|
mgr._records.clear();behavior.protectGroupLeave=true;behavior.schedule="groupformation:meeting";leader.afk=true;
|
||||||
tick(8000);behavior.schedule="instance:dungeon";tick(9000);assert(bot.group);
|
tick(8000);behavior.schedule="instance:dungeon";tick(9000);assert(bot.group);
|
||||||
behavior.protectGroupLeave=false;leader.afk=false;behavior.schedule="adventure:road";tick(9001);assert(!bot.group);bot.group=&group;
|
behavior.protectGroupLeave=false;leader.afk=false;behavior.schedule="adventure:road";tick(9001);assert(!bot.group);bot.group=&group;
|
||||||
|
// Instance travel/inside protects late arrivals from both AFK and distance.
|
||||||
|
mgr._records.clear();behavior.schedule="raid:instance";behavior.protectInstance=true;
|
||||||
|
leader.afk=true;groupTravel.separated=true;tick(10000);tick(12000);assert(bot.group);
|
||||||
|
// Explicit intent changes retain their existing behavior.
|
||||||
|
behavior.schedule="profession:city";tick(12001);assert(!bot.group);
|
||||||
|
bot.group=&group;behavior.schedule="raid:instance";tick(13000);
|
||||||
|
behavior.protectInstance=false;leader.afk=false;groupTravel.separated=false;
|
||||||
|
tick(13599);assert(bot.group);tick(13600);assert(!bot.group);
|
||||||
|
bot.group=&group;behavior.protectInstance=true;groupTravel.separated=true;tick(14000);
|
||||||
|
behavior.protectInstance=false;tick(14001);assert(!bot.group);
|
||||||
|
bot.group=&group;groupTravel.separated=false;
|
||||||
// Explicit diagnostic leave uses ordinary safe-state guards.
|
// Explicit diagnostic leave uses ordinary safe-state guards.
|
||||||
bot.combat=true;assert(!mgr.RequestLeave(&bot)&&bot.group);bot.combat=false;
|
bot.combat=true;assert(!mgr.RequestLeave(&bot)&&bot.group);bot.combat=false;
|
||||||
assert(mgr.RequestLeave(&bot)&&!bot.group);
|
assert(mgr.RequestLeave(&bot)&&!bot.group);
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
"""Run the production group-wide instance leave protection predicate."""
|
||||||
|
from pathlib import Path
|
||||||
|
import subprocess,tempfile
|
||||||
|
root=Path(__file__).resolve().parents[2]
|
||||||
|
s=(root/"src/server/game/World/AdventureBehaviorMgr.cpp").read_text()
|
||||||
|
a=s.index("bool AdventureBehaviorMgr::ProtectInstanceGroupLeave(")
|
||||||
|
b=s.index("void AdventureBehaviorMgr::InterruptForInteraction",a)
|
||||||
|
code=r'''
|
||||||
|
#include "AdventureInstancePolicy.h"
|
||||||
|
#include <cassert>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <string>
|
||||||
|
using uint32=unsigned;
|
||||||
|
namespace GameTime{time_t GetGameTime(){return 1000;}}
|
||||||
|
struct Group;
|
||||||
|
struct Map{bool dungeon=false;bool IsDungeon()const{return dungeon;}};
|
||||||
|
struct Guid{unsigned id;unsigned GetCounter()const{return id;}};
|
||||||
|
struct Player{unsigned id;Group* group=nullptr;Map map;bool online=true;bool IsInWorld()const{return online;}Group* GetGroup(){return group;}Map* GetMap(){assert(online);return ↦}Guid GetGUID(){return {id};}};
|
||||||
|
struct Reference{Player* player=nullptr;Reference* following=nullptr;Player* GetSource(){return player;}Reference* next(){return following;}};
|
||||||
|
struct Group{Reference* first=nullptr;Reference* GetFirstMember(){return first;}};
|
||||||
|
struct Location{unsigned InstanceMap=671;} location;
|
||||||
|
struct Locations{Location const* Find(std::string const& key){return key=="instance"?&location:nullptr;}} locations;auto sAdventureLocationMgr=&locations;
|
||||||
|
struct AdventureBehaviorMgr{struct Record{AdventureInstancePolicy::Visit Instance;std::string Intent,Location;time_t Deadline=0;};std::unordered_map<unsigned,Record> _records;bool ProtectInstanceGroupLeave(Player*)const;};
|
||||||
|
'''+s[a:b]+r'''
|
||||||
|
int main(){AdventureBehaviorMgr m;Player late{1},leader{2};assert(!m.ProtectInstanceGroupLeave(&late));
|
||||||
|
Reference second{&leader,nullptr},first{&late,&second};Group group{&first};late.group=leader.group=&group;
|
||||||
|
assert(!m.ProtectInstanceGroupLeave(&late));leader.map.dungeon=true;
|
||||||
|
assert(m.ProtectInstanceGroupLeave(&late)); // human leader inside, no behavior record
|
||||||
|
leader.online=false;assert(!m.ProtectInstanceGroupLeave(&late));leader.online=true;leader.map.dungeon=false;
|
||||||
|
auto& r=m._records[2];r.Instance.Active=true;
|
||||||
|
for(auto phase:{AdventureInstancePolicy::Phase::Travel,AdventureInstancePolicy::Phase::Preparing,AdventureInstancePolicy::Phase::Entering,AdventureInstancePolicy::Phase::Inside,AdventureInstancePolicy::Phase::Returning}){
|
||||||
|
r.Instance.Stage=phase;assert(m.ProtectInstanceGroupLeave(&late));}
|
||||||
|
r.Instance.Stage=AdventureInstancePolicy::Phase::Rest;r.Intent="raid";r.Location="instance";r.Deadline=2000;
|
||||||
|
assert(!m.ProtectInstanceGroupLeave(&late)); // finished visit must not retain protection
|
||||||
|
r.Instance.Active=false;assert(m.ProtectInstanceGroupLeave(&late)); // pending retry/en-route
|
||||||
|
r.Intent="instance";assert(m.ProtectInstanceGroupLeave(&late));
|
||||||
|
r.Deadline=1000;assert(!m.ProtectInstanceGroupLeave(&late));
|
||||||
|
r.Deadline=2000;r.Location="unknown";assert(!m.ProtectInstanceGroupLeave(&late));
|
||||||
|
r.Location="instance";r.Intent="profession";assert(!m.ProtectInstanceGroupLeave(&late));
|
||||||
|
first.following=nullptr;assert(!m.ProtectInstanceGroupLeave(&late));}
|
||||||
|
'''
|
||||||
|
with tempfile.TemporaryDirectory(prefix="vcore-instance-group-protection-") as d:
|
||||||
|
p=Path(d);(p/"test.cpp").write_text(code)
|
||||||
|
subprocess.run(["c++","-std=c++17","-I"+str(root/"src/server/game/World"),str(p/"test.cpp"),"-o",str(p/"test")],check=True)
|
||||||
|
subprocess.run([str(p/"test")],check=True)
|
||||||
|
print("Instance group protection: travel/preparing/entering/inside/return, human leader, offline, retry, expiry and finished visit PASS")
|
||||||
Reference in new issue
Block a user