mirror of
https://github.com/mod-playerbots/mod-playerbots.git
synced 2026-01-24 05:56:23 +00:00
Stripped down version of #1818. No new features. Refactors IsPossibleTarget in AttackersValue.cpp to a better style and makes sure pets don't attack in prohibited zones. Testing: Confirmed that aggro pets no longer attack in PVP prohibited areas, but still do outside them. Zim'Torga in Zul'Drak is a good example to test this (ID 4323). Lookout for death knights with a Risen Ally (uncontrolled and naturally aggro) now they respect PVP prohibition like their master. Note: If you manually teleport a bot that is in mid combat to a PVP prohibited area, its aggro pet might still attack, because its master is still in combat strategy. Otherwise the pet will not attack if its master has switched to non-combat.
310 lines
12 KiB
C++
310 lines
12 KiB
C++
/*
|
|
* Copyright (C) 2016+ AzerothCore <www.azerothcore.org>, released under GNU AGPL v3 license, you may redistribute it
|
|
* and/or modify it under version 3 of the License, or (at your option), any later version.
|
|
*/
|
|
|
|
#include "PetsAction.h"
|
|
|
|
#include "CharmInfo.h"
|
|
#include "Creature.h"
|
|
#include "CreatureAI.h"
|
|
#include "Pet.h"
|
|
#include "Player.h"
|
|
#include "PlayerbotAI.h"
|
|
#include "SharedDefines.h"
|
|
|
|
bool PetsAction::Execute(Event event)
|
|
{
|
|
// Extract the command parameter from the event (e.g., "aggressive", "defensive", "attack", etc.)
|
|
std::string param = event.getParam();
|
|
if (param.empty() && !defaultCmd.empty())
|
|
{
|
|
param = defaultCmd;
|
|
}
|
|
|
|
if (param.empty())
|
|
{
|
|
// If no parameter is provided, show usage instructions and return.
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_usage_error", "Usage: pet <aggressive|defensive|passive|stance|attack|follow|stay>", {});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
|
|
Player* bot = botAI->GetBot();
|
|
|
|
// Collect all controlled pets and guardians, except totems, into the targets vector.
|
|
std::vector<Creature*> targets;
|
|
Pet* pet = bot->GetPet();
|
|
if (pet)
|
|
targets.push_back(pet);
|
|
|
|
for (Unit::ControlSet::const_iterator itr = bot->m_Controlled.begin(); itr != bot->m_Controlled.end(); ++itr)
|
|
{
|
|
Creature* creature = dynamic_cast<Creature*>(*itr);
|
|
if (!creature)
|
|
continue;
|
|
if (pet && creature == pet)
|
|
continue;
|
|
if (creature->IsTotem())
|
|
continue;
|
|
targets.push_back(creature);
|
|
}
|
|
|
|
// If no pets or guardians are found, notify and return.
|
|
if (targets.empty())
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_no_pet_error", "You have no pet or guardian pet.", {});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
|
|
ReactStates react;
|
|
std::string stanceText;
|
|
|
|
// Handle stance commands: aggressive, defensive, or passive.
|
|
if (param == "aggressive")
|
|
{
|
|
react = REACT_AGGRESSIVE;
|
|
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_aggressive", "aggressive", {});
|
|
}
|
|
else if (param == "defensive")
|
|
{
|
|
react = REACT_DEFENSIVE;
|
|
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_defensive", "defensive", {});
|
|
}
|
|
else if (param == "passive")
|
|
{
|
|
react = REACT_PASSIVE;
|
|
stanceText = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_passive", "passive", {});
|
|
}
|
|
// The "stance" command simply reports the current stance of each pet/guardian.
|
|
else if (param == "stance")
|
|
{
|
|
for (Creature* target : targets)
|
|
{
|
|
std::string type = target->IsPet() ?
|
|
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_pet", "pet", {}) :
|
|
sPlayerbotTextMgr->GetBotTextOrDefault("pet_type_guardian", "guardian", {});
|
|
std::string name = target->GetName();
|
|
std::string stance;
|
|
switch (target->GetReactState())
|
|
{
|
|
case REACT_AGGRESSIVE:
|
|
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_aggressive", "aggressive", {});
|
|
break;
|
|
case REACT_DEFENSIVE:
|
|
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_defensive", "defensive", {});
|
|
break;
|
|
case REACT_PASSIVE:
|
|
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_passive", "passive", {});
|
|
break;
|
|
default:
|
|
stance = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_unknown", "unknown", {});
|
|
break;
|
|
}
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_report", "Current stance of %type \"%name\": %stance.",
|
|
{{"type", type}, {"name", name}, {"stance", stance}});
|
|
botAI->TellMaster(text);
|
|
}
|
|
return true;
|
|
}
|
|
// The "attack" command forces pets/guardians to attack the master's selected target.
|
|
else if (param == "attack")
|
|
{
|
|
// Try to get the master's selected target.
|
|
Player* master = botAI->GetMaster();
|
|
Unit* targetUnit = nullptr;
|
|
|
|
if (master)
|
|
{
|
|
ObjectGuid masterTargetGuid = master->GetTarget();
|
|
if (!masterTargetGuid.IsEmpty())
|
|
{
|
|
targetUnit = botAI->GetUnit(masterTargetGuid);
|
|
}
|
|
}
|
|
|
|
// If no valid target is selected, show an error and return.
|
|
if (!targetUnit)
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_no_target_error", "No valid target selected by master.", {});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
if (!targetUnit->IsAlive())
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_target_dead_error", "Target is not alive.", {});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
if (!bot->IsValidAttackTarget(targetUnit))
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_invalid_target_error", "Target is not a valid attack target for the bot.", {});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
if (sPlayerbotAIConfig->IsPvpProhibited(bot->GetZoneId(), bot->GetAreaId())
|
|
&& (targetUnit->IsPlayer() || targetUnit->IsPet()))
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_pvp_prohibited_error", "I cannot command my pet to attack players in PvP prohibited areas.", {});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
|
|
bool didAttack = false;
|
|
// For each controlled pet/guardian, command them to attack the selected target.
|
|
for (Creature* petCreature : targets)
|
|
{
|
|
CharmInfo* charmInfo = petCreature->GetCharmInfo();
|
|
if (!charmInfo)
|
|
continue;
|
|
|
|
petCreature->ClearUnitState(UNIT_STATE_FOLLOW);
|
|
// Only command attack if not already attacking the target, or if not currently under command attack.
|
|
if (petCreature->GetVictim() != targetUnit ||
|
|
(petCreature->GetVictim() == targetUnit && !charmInfo->IsCommandAttack()))
|
|
{
|
|
if (petCreature->GetVictim())
|
|
petCreature->AttackStop();
|
|
|
|
if (!petCreature->IsPlayer() && petCreature->ToCreature()->IsAIEnabled)
|
|
{
|
|
// For AI-enabled creatures (NPC pets/guardians): issue attack command and set flags.
|
|
charmInfo->SetIsCommandAttack(true);
|
|
charmInfo->SetIsAtStay(false);
|
|
charmInfo->SetIsFollowing(false);
|
|
charmInfo->SetIsCommandFollow(false);
|
|
charmInfo->SetIsReturning(false);
|
|
|
|
petCreature->ToCreature()->AI()->AttackStart(targetUnit);
|
|
|
|
didAttack = true;
|
|
}
|
|
else // For charmed player pets/guardians
|
|
{
|
|
if (petCreature->GetVictim() && petCreature->GetVictim() != targetUnit)
|
|
petCreature->AttackStop();
|
|
|
|
charmInfo->SetIsCommandAttack(true);
|
|
charmInfo->SetIsAtStay(false);
|
|
charmInfo->SetIsFollowing(false);
|
|
charmInfo->SetIsCommandFollow(false);
|
|
charmInfo->SetIsReturning(false);
|
|
|
|
petCreature->Attack(targetUnit, true);
|
|
didAttack = true;
|
|
}
|
|
}
|
|
}
|
|
// Inform the master if the command succeeded or failed.
|
|
if (didAttack && sPlayerbotAIConfig->petChatCommandDebug == 1)
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_attack_success", "Pet commanded to attack your target.", {});
|
|
botAI->TellMaster(text);
|
|
}
|
|
else if (!didAttack)
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_attack_failed", "Pet did not attack. (Already attacking or unable to attack target)", {});
|
|
botAI->TellError(text);
|
|
}
|
|
return didAttack;
|
|
}
|
|
// The "follow" command makes all pets/guardians follow the bot.
|
|
else if (param == "follow")
|
|
{
|
|
botAI->PetFollow();
|
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_follow_success", "Pet commanded to follow.", {});
|
|
botAI->TellMaster(text);
|
|
}
|
|
return true;
|
|
}
|
|
// The "stay" command causes all pets/guardians to stop and stay in place.
|
|
else if (param == "stay")
|
|
{
|
|
for (Creature* target : targets)
|
|
{
|
|
// If not already in controlled motion, stop movement and set to idle.
|
|
bool controlledMotion =
|
|
target->GetMotionMaster()->GetMotionSlotType(MOTION_SLOT_CONTROLLED) != NULL_MOTION_TYPE;
|
|
if (!controlledMotion)
|
|
{
|
|
target->StopMovingOnCurrentPos();
|
|
target->GetMotionMaster()->Clear(false);
|
|
target->GetMotionMaster()->MoveIdle();
|
|
}
|
|
|
|
CharmInfo* charmInfo = target->GetCharmInfo();
|
|
if (charmInfo)
|
|
{
|
|
// Set charm/pet state flags for "stay".
|
|
charmInfo->SetCommandState(COMMAND_STAY);
|
|
charmInfo->SetIsCommandAttack(false);
|
|
charmInfo->SetIsCommandFollow(false);
|
|
charmInfo->SetIsFollowing(false);
|
|
charmInfo->SetIsReturning(false);
|
|
charmInfo->SetIsAtStay(!controlledMotion);
|
|
charmInfo->SaveStayPosition(controlledMotion);
|
|
if (target->ToPet())
|
|
target->ToPet()->ClearCastWhenWillAvailable();
|
|
|
|
charmInfo->SetForcedSpell(0);
|
|
charmInfo->SetForcedTargetGUID();
|
|
}
|
|
}
|
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stay_success", "Pet commanded to stay.", {});
|
|
botAI->TellMaster(text);
|
|
}
|
|
return true;
|
|
}
|
|
// Unknown command: show usage instructions and return.
|
|
else
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_unknown_command_error", "Unknown pet command: %param. Use: pet <aggressive|defensive|passive|stance|attack|follow|stay>",
|
|
{{"param", param}});
|
|
botAI->TellError(text);
|
|
return false;
|
|
}
|
|
|
|
// For stance commands, apply the chosen stance to all targets.
|
|
for (Creature* target : targets)
|
|
{
|
|
target->SetReactState(react);
|
|
CharmInfo* charmInfo = target->GetCharmInfo();
|
|
if (charmInfo)
|
|
charmInfo->SetPlayerReactState(react);
|
|
}
|
|
|
|
// Inform the master of the new stance if debug is enabled.
|
|
if (sPlayerbotAIConfig->petChatCommandDebug == 1)
|
|
{
|
|
std::string text = sPlayerbotTextMgr->GetBotTextOrDefault(
|
|
"pet_stance_set_success", "Pet stance set to %stance.",
|
|
{{"stance", stanceText}});
|
|
botAI->TellMaster(text);
|
|
}
|
|
|
|
return true;
|
|
} |