Files
Viano-Core/src/server/game/World/AdventureInteractionMgr.cpp
T

255 lines
14 KiB
C++

#include "AdventureInteractionMgr.h"
#include "AdventureInteractionPolicy.h"
#include "AdventureBehaviorMgr.h"
#include "AdventureCombatMgr.h"
#include "ClientlessLuaEngine.h"
#include "ClientlessPlayerMgr.h"
#include "ClientlessTravelMgr.h"
#include "ClientlessActionExecutorMgr.h"
#include "ClientlessTravelGraphMgr.h"
#include "Chat.h"
#include "Creature.h"
#include "GameTime.h"
#include "Group.h"
#include "Map.h"
#include "MotionMaster.h"
#include "ObjectAccessor.h"
#include "Opcodes.h"
#include "PartyPackets.h"
#include "Player.h"
#include "Random.h"
#include "WorldPacket.h"
#include "WorldSession.h"
#include "Log.h"
#include <vector>
namespace
{
float Sociability(Player* p)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
if(profile){auto it=profile->Personality.find("sociability");if(it!=profile->Personality.end())return it->second;}
return 0.5f;
}
float Loyalty(Player* p)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
if(profile){auto it=profile->Personality.find("group_loyalty");if(it!=profile->Personality.end())return std::max(0.0f,std::min(1.0f,it->second));}
return 0.25f;
}
bool German(Player* p)
{
auto profile=sClientlessLuaEngine->GetAdventureProfile(p->GetGUID().GetCounter());
return profile && profile->Locale=="deDE";
}
bool HostileEmote(uint32 emote)
{return emote==TEXT_EMOTE_RUDE || emote==TEXT_EMOTE_THREATEN || emote==TEXT_EMOTE_ROAR;}
bool FriendlyEmote(uint32 emote)
{
switch(emote)
{
case TEXT_EMOTE_GREET:case TEXT_EMOTE_HELLO:case TEXT_EMOTE_WAVE:case TEXT_EMOTE_BOW:
case TEXT_EMOTE_SALUTE:case TEXT_EMOTE_SMILE:case TEXT_EMOTE_THANK:return true;
default:return false;
}
}
}
AdventureInteractionMgr* AdventureInteractionMgr::instance(){static AdventureInteractionMgr mgr;return &mgr;}
bool AdventureInteractionMgr::Visible(Player* p,Player* other,float distance) const
{
return other && other!=p && other->IsAlive() && other->IsInWorld() && p->IsInMap(other) &&
p->IsInPhase(other) && p->CanSeeOrDetect(other) && other->CanSeeOrDetect(p) && p->GetExactDist(other)<=distance && p->IsWithinLOSInMap(other);
}
void AdventureInteractionMgr::PartySay(Player* p,char const* german,char const* english)
{
auto group=p->GetGroup();if(!group)return;
WorldPacket packet;
ChatHandler::BuildChatPacket(packet,CHAT_MSG_PARTY,p->GetTeam()==ALLIANCE?LANG_COMMON:LANG_ORCISH,p,nullptr,German(p)?german:english);
group->BroadcastPacket(&packet,false);
}
void AdventureInteractionMgr::Leave(Player* p,Record& r,char const* reason)
{
PartySay(p,"Ich muss weiter. Gute Reise!","I must be going. Safe travels!");
TC_LOG_INFO("server.adventurebot","INTERACTION_GROUP_LEAVE guid=%u reason='%s'",p->GetGUID().GetCounter(),reason);
p->GetGroup()->RemoveMember(p->GetGUID(),GROUP_REMOVEMETHOD_LEAVE);
sAdventureBehaviorMgr->InterruptForInteraction(p);
r.Grouped=false;r.Leader.Clear();r.Invite.Clear();r.Partner.Clear();r.SceneUntil=0;
}
void AdventureInteractionMgr::Emote(Player* p,Player* source,uint32 emote)
{
if(!Visible(p,source,25) || p->IsInCombat() || !p->IsAlive() || p->GetMap()->IsDungeon() ||
p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed())return;
auto now=GameTime::GetGameTime();auto& r=_records[p->GetGUID().GetCounter()];
if(r.Partner || now<r.NextSocial || (!FriendlyEmote(emote) && !HostileEmote(emote)))return;
auto it=r.Encounters.find(source->GetGUID());if(it!=r.Encounters.end() && now<it->second)return;
r.Partner=source->GetGUID();r.Reply=emote;r.ReplyAt=now+urand(1,3);r.SceneUntil=r.ReplyAt+2;
r.NextSocial=now+15;r.Encounters[source->GetGUID()]=now+120;
}
void AdventureInteractionMgr::Reply(Player* p,Player* other,uint32 emote)
{
p->SetStandState(UNIT_STAND_STATE_STAND);p->SetFacingToObject(other);
// Hostility alone never grants permission to attack. Native attack checks
// remain in combat; these are visual reactions without combat side effects.
bool friendly=p->IsFriendlyTo(other) && !HostileEmote(emote);
p->HandleEmoteCommand(friendly?(emote==TEXT_EMOTE_BOW?EMOTE_ONESHOT_BOW:EMOTE_ONESHOT_WAVE):EMOTE_ONESHOT_NO);
if(emote==TEXT_EMOTE_GREET && friendly && urand(0,2)==0)
p->Say(German(p)?"Gute Reise!":"Safe travels!",p->GetTeam()==ALLIANCE?LANG_COMMON:LANG_ORCISH,other);
TC_LOG_INFO("server.adventurebot","INTERACTION_EMOTE guid=%u other=%u friendly=%u",p->GetGUID().GetCounter(),other->GetGUID().GetCounter(),friendly);
}
void AdventureInteractionMgr::CombatHelp(Player* p,ObjectGuid helper)
{ _records[p->GetGUID().GetCounter()].Helper=helper; }
void AdventureInteractionMgr::Offline(Player* p){_records.erase(p->GetGUID().GetCounter());}
bool AdventureInteractionMgr::Update(Player* p,uint32 diff)
{
auto& r=_records[p->GetGUID().GetCounter()];auto now=GameTime::GetGameTime();
if(r.Timer>diff){r.Timer-=diff;return r.Grouped || r.SceneUntil>now;}
r.Timer=1000;
bool unsafe=!p->IsAlive() || p->IsInCombat() || sAdventureCombatMgr->Busy(p) ||
p->IsBeingTeleported() || p->GetTransport() || p->IsInFlight() || p->IsCharmed();
if(unsafe){r.Partner.Clear();r.SceneUntil=0;return p->GetGroup()!=nullptr;}
for(auto it=r.Encounters.begin();it!=r.Encounters.end();)if(now>=it->second)it=r.Encounters.erase(it);else ++it;
if(r.Helper)
{
auto helper=ObjectAccessor::FindPlayer(r.Helper);r.Helper.Clear();
if(Visible(p,helper,25) && p->IsFriendlyTo(helper))
p->Say(German(p)?"Danke für die Hilfe!":"Thank you for the help!",p->GetTeam()==ALLIANCE?LANG_COMMON:LANG_ORCISH,helper);
}
if(auto invite=p->GetGroupInvite())
{
auto leader=invite->GetLeaderGUID();
if(r.Invite!=leader){r.Invite=leader;r.InviteAt=now+urand(2,4);}
if(now>=r.InviteAt)
{
auto inviter=ObjectAccessor::FindPlayer(leader);
bool accept=inviter && inviter->IsAlive() && p->IsFriendlyTo(inviter) && !p->GetMap()->IsDungeon() &&
!inviter->GetSession()->IsClientless() && frand(0,1)<AdventureInteractionPolicy::Willingness(sAdventureBehaviorMgr->InteractionIntent(p),Sociability(p));
WorldPackets::Party::PartyInviteResponse response{WorldPacket(CMSG_PARTY_INVITE_RESPONSE)};response.Accept=accept;
p->GetSession()->HandlePartyInviteResponseOpcode(response);r.Invite.Clear();
TC_LOG_INFO("server.adventurebot","INTERACTION_INVITE guid=%u leader=%u accepted=%u",p->GetGUID().GetCounter(),leader.GetCounter(),accept);
}
}
else r.Invite.Clear();
if(r.Partner)
{
auto other=ObjectAccessor::FindPlayer(r.Partner);
if(!Visible(p,other,25) || now>=r.SceneUntil){r.Partner.Clear();r.SceneUntil=0;}
else if(now>=r.ReplyAt)
{
sAdventureBehaviorMgr->InterruptForInteraction(p);Reply(p,other,r.Reply);r.ReplyAt=r.SceneUntil+1;
}
if(r.Partner)return true;
}
if(auto group=p->GetGroup())
{
auto leader=ObjectAccessor::FindPlayer(group->GetLeaderGUID());
if(!r.Grouped)
{
r.Grouped=true;r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);
r.Partner.Clear();r.SceneUntil=0;
sAdventureBehaviorMgr->InterruptForInteraction(p);
PartySay(p,"Ich begleite euch eine Weile.","I'll accompany you for a while.");
}
if(r.Leader!=group->GetLeaderGUID())
{
r.Leader=group->GetLeaderGUID();r.LastActivity=now;r.AfkSince=0;
if(leader){r.LeaderPosition.Relocate(leader);r.LeaderMap=leader->GetMapId();r.Selection=leader->GetTarget();}
}
if(leader)
{
r.LastActivity=std::max(r.LastActivity,sClientlessPlayerMgr->LastActivity(leader->GetGUID()));
if(r.LeaderMap!=leader->GetMapId() || r.LeaderPosition.GetExactDist(leader)>0.1f ||
r.LeaderPosition.GetOrientation()!=leader->GetOrientation() || r.Selection!=leader->GetTarget() ||
leader->IsInCombat() || leader->HasUnitState(UNIT_STATE_CASTING))r.LastActivity=now;
r.LeaderPosition.Relocate(leader);r.LeaderMap=leader->GetMapId();r.Selection=leader->GetTarget();
if(leader->isAFK()){if(!r.AfkSince)r.AfkSince=now;}else r.AfkSince=0;
}
if(AdventureInteractionPolicy::CanLeave(p->IsAlive(),p->IsInCombat(),p->GetMap()->IsDungeon(),p->GetTransport() || p->IsInFlight()))
{
if(r.Schedule!=sAdventureBehaviorMgr->InteractionSchedule(p))
{
r.Schedule=sAdventureBehaviorMgr->InteractionSchedule(p);
if(frand(0,1)>=Loyalty(p)){Leave(p,r,"intent-change");return false;}
}
if(AdventureInteractionPolicy::LeaderExpired(now,r.AfkSince?r.AfkSince:r.LastActivity)){Leave(p,r,"leader-afk");return false;}
}
// Existing warrior executor supplies damage assistance. Roles/healing
// deliberately remain a later profile-driven extension.
for(auto member=group->GetFirstMember();member;member=member->next())
{
auto ally=member->GetSource();if(!ally || !ally->IsAlive() || !p->IsInMap(ally) || p->GetExactDist(ally)>40)continue;
auto target=ally->IsInCombat()?ally->GetVictim():nullptr;
if(!target)for(auto attacker:ally->getAttackers())if(attacker->GetVictim()==ally){target=attacker;break;}
if(target && sAdventureCombatMgr->Assist(p,target))return true;
}
if(!leader || leader==p || !leader->IsAlive())return true;
auto execution=sClientlessActionExecutorMgr->GetStatus(p->GetGUID().GetCounter());
if(execution && (execution->State==ClientlessActionExecutionState::Moving || execution->State==ClientlessActionExecutionState::SelectingTarget))return true;
if(!p->IsInMap(leader) || p->GetExactDist(leader)>=100)
{
if(now>=r.NextRoute)
{
r.NextRoute=now+15;
std::string node;float distance=0;
sClientlessTravelGraphMgr->EnsureLoaded();
if(sClientlessTravelGraphMgr->FindNearestNodeToPosition(leader->GetMapId(),leader->GetPositionX(),leader->GetPositionY(),leader->GetPositionZ(),node,distance) && distance<1000)
{
ClientlessAction action;action.Name="travel_to_node";action.Parameters["node"]=node;
sClientlessPlayerMgr->QueueAction(p->GetGUID().GetCounter(),std::move(action));
}
}
return true;
}
if(!p->IsInPhase(leader) || !p->CanSeeOrDetect(leader))return true;
if(p->GetExactDist(leader)>5 && p->GetExactDist(leader)<100)
{
auto nav=sClientlessTravelMgr->GetStatus(p->GetGUID().GetCounter());
if(!nav || nav->State!=ClientlessTravelState::Moving || nav->Target.GetExactDist(leader)>5)
{
Position end;leader->GetNearPoint(p,end.m_positionX,end.m_positionY,end.m_positionZ,3,leader->GetOrientation()+3.14159265f);
ClientlessPathDiagnostics path;
if(sClientlessTravelMgr->AnalyzePath(p,end,path) && path.Complete && !path.Suspicious && path.PathLength<150)
sClientlessTravelMgr->StartTravel(p,end);
}
}
else if(p->GetExactDist(leader)<=5)sClientlessTravelMgr->CancelTravel(p->GetGUID().GetCounter());
return true;
}
if(r.Grouped){r.Grouped=false;r.Leader.Clear();sAdventureBehaviorMgr->InterruptForInteraction(p);}
auto intent=sAdventureBehaviorMgr->InteractionIntent(p);
if(now>=r.NextEncounter && now>=r.NextSocial && !p->GetMap()->IsDungeon() && (intent=="social" || intent=="leisure" || intent=="rest"))
{
r.NextEncounter=now+urand(30,60);
std::vector<Player*> people;p->GetPlayerListInGrid(people,12);
for(auto other:people)
{
if(!Visible(p,other,12) || !sClientlessLuaEngine->GetAdventureProfile(other->GetGUID().GetCounter()) ||
p->GetGUID().GetCounter()>=other->GetGUID().GetCounter() || other->IsInCombat() || other->GetGroup())continue;
auto& peer=_records[other->GetGUID().GetCounter()];
auto peerIntent=sAdventureBehaviorMgr->InteractionIntent(other);
if((peerIntent!="social" && peerIntent!="leisure" && peerIntent!="rest") || other->HasUnitState(UNIT_STATE_CASTING))continue;
if(peer.Partner || peer.NextSocial>now || r.Encounters.count(other->GetGUID()) || frand(0,1)>Sociability(p))continue;
// One initiator reserves both participants; animation replies do
// not generate incoming text-emote events, preventing loops.
r.Partner=other->GetGUID();r.Reply=p->IsFriendlyTo(other)?TEXT_EMOTE_GREET:TEXT_EMOTE_THREATEN;
r.ReplyAt=now+1;r.SceneUntil=now+5;r.Encounters[other->GetGUID()]=now+300;
peer.Partner=p->GetGUID();peer.Reply=r.Reply;peer.ReplyAt=now+3;peer.SceneUntil=now+5;
peer.NextSocial=now+60;peer.Encounters[p->GetGUID()]=now+300;
return true;
}
}
return false;
}
void AdventureInteractionMgr::Report(ChatHandler* handler,Player* p) const
{
auto it=_records.find(p->GetGUID().GetCounter());
if(it==_records.end()){handler->SendSysMessage("No interaction state yet.");return;}
auto const& r=it->second;auto now=GameTime::GetGameTime();
handler->PSendSysMessage("Interactions guid=%u grouped=%u leader=%u partner=%u intent='%s' schedule='%s'",
p->GetGUID().GetCounter(),p->GetGroup()?1:0,r.Leader.GetCounter(),r.Partner.GetCounter(),
sAdventureBehaviorMgr->InteractionIntent(p).c_str(),r.Schedule.c_str());
handler->PSendSysMessage(" leaderIdleSeconds=%llu afkSeconds=%llu sociability=%.2f loyalty=%.2f",
static_cast<unsigned long long>(r.LastActivity && now>=r.LastActivity?now-r.LastActivity:0),
static_cast<unsigned long long>(r.AfkSince && now>=r.AfkSince?now-r.AfkSince:0),Sociability(p),Loyalty(p));
}