Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Core/UnitAI: Rework creature-controlled player behavior.
- Removed hacked control mechanism, use proper PlayerAI instead - Port old hacky code to new SimpleCharmedPlayerAI class - Make adjustments to aforementioned code to fix bugs: - Properly clean up movement after charm ends - Only try to attack a target if charmer is engaged in combat
- Loading branch information
Showing
13 changed files
with
304 additions
and
102 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,195 @@ | ||
/* | ||
* Copyright (C) 2016-2016 TrinityCore <http://www.trinitycore.org/> | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License as published by the | ||
* Free Software Foundation; either version 2 of the License, or (at your | ||
* option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#include "PlayerAI.h" | ||
#include "SpellAuras.h" | ||
#include "SpellAuraEffects.h" | ||
|
||
enum Spells | ||
{ | ||
/* Generic */ | ||
SPELL_AUTO_SHOT = 75, | ||
SPELL_SHOOT = 3018, | ||
SPELL_THROW = 2764, | ||
SPELL_SHOOT_WAND = 5019, | ||
|
||
/* Priest */ | ||
SPELL_SHADOWFORM = 15473, | ||
|
||
/* Shaman */ | ||
SPELL_STORMSTRIKE = 17364, | ||
|
||
/* Druid */ | ||
SPELL_MOONKIN_FORM = 24858 | ||
}; | ||
PlayerAI::PlayerAI(Player* player) : UnitAI(static_cast<Unit*>(player)), me(player), _isRangedAttacker(false) | ||
{ | ||
switch (me->getClass()) | ||
{ | ||
case CLASS_WARRIOR: | ||
_isRangedAttacker = false; | ||
break; | ||
case CLASS_PALADIN: | ||
_isRangedAttacker = false; | ||
break; | ||
case CLASS_HUNTER: | ||
{ | ||
// check if we have a ranged weapon equipped | ||
Item const* rangedSlot = me->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); | ||
if (ItemTemplate const* rangedTemplate = rangedSlot ? rangedSlot->GetTemplate() : nullptr) | ||
if ((1 << rangedTemplate->SubClass) & ITEM_SUBCLASS_MASK_WEAPON_RANGED) | ||
{ | ||
_isRangedAttacker = true; | ||
break; | ||
} | ||
_isRangedAttacker = false; | ||
break; | ||
} | ||
case CLASS_ROGUE: | ||
_isRangedAttacker = false; | ||
break; | ||
case CLASS_PRIEST: | ||
_isRangedAttacker = me->HasSpell(SPELL_SHADOWFORM); | ||
break; | ||
case CLASS_DEATH_KNIGHT: | ||
_isRangedAttacker = false; | ||
break; | ||
case CLASS_SHAMAN: | ||
_isRangedAttacker = !me->HasSpell(SPELL_STORMSTRIKE); | ||
break; | ||
case CLASS_MAGE: | ||
_isRangedAttacker = true; | ||
break; | ||
case CLASS_WARLOCK: | ||
_isRangedAttacker = true; | ||
break; | ||
case CLASS_DRUID: | ||
_isRangedAttacker = me->HasAura(SPELL_MOONKIN_FORM); | ||
break; | ||
default: | ||
TC_LOG_WARN("entities.unit", "Possessed player %s (possessed by %s) does not have any recognized class (class = %u).", me->GetGUID().ToString().c_str(), me->GetCharmerGUID().ToString().c_str(), me->getClass()); | ||
break; | ||
} | ||
} | ||
|
||
void PlayerAI::DoRangedAttackIfReady() | ||
{ | ||
if (me->HasUnitState(UNIT_STATE_CASTING)) | ||
return; | ||
|
||
if (!me->isAttackReady(RANGED_ATTACK)) | ||
return; | ||
|
||
Unit* victim = me->GetVictim(); | ||
if (!victim) | ||
return; | ||
|
||
uint32 rangedAttackSpell = 0; | ||
|
||
Item const* rangedItem = me->GetItemByPos(INVENTORY_SLOT_BAG_0, EQUIPMENT_SLOT_RANGED); | ||
if (ItemTemplate const* rangedTemplate = rangedItem ? rangedItem->GetTemplate() : nullptr) | ||
{ | ||
switch (rangedTemplate->SubClass) | ||
{ | ||
case ITEM_SUBCLASS_WEAPON_BOW: | ||
case ITEM_SUBCLASS_WEAPON_GUN: | ||
case ITEM_SUBCLASS_WEAPON_CROSSBOW: | ||
rangedAttackSpell = SPELL_SHOOT; | ||
break; | ||
case ITEM_SUBCLASS_WEAPON_THROWN: | ||
rangedAttackSpell = SPELL_THROW; | ||
break; | ||
case ITEM_SUBCLASS_WEAPON_WAND: | ||
rangedAttackSpell = SPELL_SHOOT_WAND; | ||
break; | ||
} | ||
} | ||
|
||
std::cout << "Selected " << rangedAttackSpell << std::endl; | ||
|
||
if (!rangedAttackSpell) | ||
return; | ||
|
||
me->CastSpell(victim, rangedAttackSpell, TRIGGERED_CAST_DIRECTLY); | ||
me->resetAttackTimer(RANGED_ATTACK); | ||
} | ||
|
||
void PlayerAI::DoAutoAttackIfReady() | ||
{ | ||
if (IsRangedAttacker()) | ||
DoRangedAttackIfReady(); | ||
else | ||
DoMeleeAttackIfReady(); | ||
} | ||
|
||
void SimpleCharmedPlayerAI::UpdateAI(const uint32 /*diff*/) | ||
{ | ||
Creature* charmer = me->GetCharmer()->ToCreature(); | ||
|
||
//kill self if charm aura has infinite duration | ||
if (charmer->IsInEvadeMode()) | ||
{ | ||
Player::AuraEffectList const& auras = me->GetAuraEffectsByType(SPELL_AURA_MOD_CHARM); | ||
for (Player::AuraEffectList::const_iterator iter = auras.begin(); iter != auras.end(); ++iter) | ||
if ((*iter)->GetCasterGUID() == charmer->GetGUID() && (*iter)->GetBase()->IsPermanent()) | ||
{ | ||
me->Kill(me); | ||
return; | ||
} | ||
} | ||
|
||
if (charmer->IsInCombat()) | ||
{ | ||
Unit* target = me->GetVictim(); | ||
if (!target || !charmer->IsValidAttackTarget(target)) | ||
{ | ||
target = charmer->SelectNearestTarget(); | ||
if (!target) | ||
return; | ||
|
||
if (IsRangedAttacker()) | ||
AttackStartCaster(target, 28.0f); | ||
else | ||
AttackStart(target); | ||
} | ||
} | ||
else | ||
{ | ||
me->AttackStop(); | ||
me->CastStop(); | ||
me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); | ||
} | ||
|
||
DoAutoAttackIfReady(); | ||
} | ||
|
||
void SimpleCharmedPlayerAI::OnCharmed(bool apply) | ||
{ | ||
if (apply) | ||
{ | ||
me->CastStop(); | ||
me->AttackStop(); | ||
} | ||
else | ||
{ | ||
me->CastStop(); | ||
me->AttackStop(); | ||
// @todo only voluntary movement (don't cancel stuff like death grip or charge mid-animation) | ||
me->GetMotionMaster()->Clear(); | ||
me->StopMoving(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
/* | ||
* Copyright (C) 2016-2016 TrinityCore <http://www.trinitycore.org/> | ||
* | ||
* This program is free software; you can redistribute it and/or modify it | ||
* under the terms of the GNU General Public License as published by the | ||
* Free Software Foundation; either version 2 of the License, or (at your | ||
* option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT | ||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
* more details. | ||
* | ||
* You should have received a copy of the GNU General Public License along | ||
* with this program. If not, see <http://www.gnu.org/licenses/>. | ||
*/ | ||
|
||
#ifndef TRINITY_PLAYERAI_H | ||
#define TRINITY_PLAYERAI_H | ||
|
||
#include "UnitAI.h" | ||
#include "Player.h" | ||
#include "Creature.h" | ||
|
||
class PlayerAI : public UnitAI | ||
{ | ||
public: | ||
explicit PlayerAI(Player* player); | ||
|
||
void OnCharmed(bool /*apply*/) override { } // charm AI application for players is handled by Unit::SetCharmedBy / Unit::RemoveCharmedBy | ||
|
||
protected: | ||
Player* const me; | ||
void SetIsRangedAttacker(bool state) { _isRangedAttacker = state; } | ||
bool IsRangedAttacker() const { return _isRangedAttacker; } | ||
|
||
void DoRangedAttackIfReady(); | ||
void DoAutoAttackIfReady(); | ||
|
||
private: | ||
bool _isRangedAttacker; | ||
}; | ||
|
||
class SimpleCharmedPlayerAI : public PlayerAI | ||
{ | ||
public: | ||
SimpleCharmedPlayerAI(Player* player) : PlayerAI(player) { } | ||
void UpdateAI(uint32 diff) override; | ||
void OnCharmed(bool apply) override; | ||
}; | ||
|
||
#endif |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.