diff options
author | Treeston <treeston.mmoc@gmail.com> | 2016-02-24 13:39:31 +0100 |
---|---|---|
committer | Shauren <shauren.trinity@gmail.com> | 2016-04-08 20:16:15 +0200 |
commit | adc7ee0c517ae80b24a56367ee3ae7d498f2827d (patch) | |
tree | fc7ccfa1633a2cc95d1103976925321568d0d47f /src | |
parent | 765bd286b76ded31cae099e8bfff9c8dcb664f0a (diff) |
Merge pull request #16644 from Treeston/3.3.5-customcharmai
Core/UnitAI: Rework creature-controlled player behavior.
(cherry picked from commit 4e4b2b9a138650ebe6087e18dc392f08dafe4e37)
Diffstat (limited to 'src')
-rw-r--r-- | src/server/game/AI/CoreAI/UnitAI.cpp | 30 | ||||
-rw-r--r-- | src/server/game/AI/CoreAI/UnitAI.h | 17 | ||||
-rw-r--r-- | src/server/game/AI/CreatureAI.h | 6 | ||||
-rw-r--r-- | src/server/game/AI/PlayerAI/PlayerAI.cpp | 184 | ||||
-rw-r--r-- | src/server/game/AI/PlayerAI/PlayerAI.h | 52 | ||||
-rw-r--r-- | src/server/game/Entities/Creature/Creature.h | 2 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.cpp | 38 | ||||
-rw-r--r-- | src/server/game/Entities/Player/Player.h | 4 | ||||
-rw-r--r-- | src/server/game/Entities/Unit/Unit.cpp | 32 | ||||
-rw-r--r-- | src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp | 12 |
10 files changed, 285 insertions, 92 deletions
diff --git a/src/server/game/AI/CoreAI/UnitAI.cpp b/src/server/game/AI/CoreAI/UnitAI.cpp index f610138939f..22971062fcc 100644 --- a/src/server/game/AI/CoreAI/UnitAI.cpp +++ b/src/server/game/AI/CoreAI/UnitAI.cpp @@ -228,36 +228,6 @@ void UnitAI::FillAISpellInfo() } } -//Enable PlayerAI when charmed -void PlayerAI::OnCharmed(bool apply) -{ - me->IsAIEnabled = apply; -} - -void SimpleCharmedAI::UpdateAI(const uint32 /*diff*/) -{ - Creature* charmer = me->GetCharmer()->ToCreature(); - - //kill self if charm aura has infinite duration - if (charmer->IsInEvadeMode()) - { - Unit::AuraEffectList const& auras = me->GetAuraEffectsByType(SPELL_AURA_MOD_CHARM); - for (Unit::AuraEffectList::const_iterator iter = auras.begin(); iter != auras.end(); ++iter) - if ((*iter)->GetCasterGUID() == charmer->GetGUID() && (*iter)->GetBase()->IsPermanent()) - { - charmer->Kill(me); - return; - } - } - - if (!charmer->IsInCombat()) - me->GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, me->GetFollowAngle()); - - Unit* target = me->GetVictim(); - if (!target || !charmer->IsValidAttackTarget(target)) - AttackStart(charmer->SelectNearestTargetInAttackDistance()); -} - SpellTargetSelector::SpellTargetSelector(Unit* caster, uint32 spellId) : _caster(caster), _spellInfo(sSpellMgr->GetSpellInfo(spellId)) { diff --git a/src/server/game/AI/CoreAI/UnitAI.h b/src/server/game/AI/CoreAI/UnitAI.h index ea001ceda32..6f7f10a8a11 100644 --- a/src/server/game/AI/CoreAI/UnitAI.h +++ b/src/server/game/AI/CoreAI/UnitAI.h @@ -264,21 +264,4 @@ class TC_GAME_API UnitAI UnitAI& operator=(UnitAI const& right) = delete; }; -class TC_GAME_API PlayerAI : public UnitAI -{ - protected: - Player* const me; - public: - explicit PlayerAI(Player* player) : UnitAI((Unit*)player), me(player) { } - - void OnCharmed(bool apply) override; -}; - -class TC_GAME_API SimpleCharmedAI : public PlayerAI -{ - public: - void UpdateAI(uint32 diff) override; - SimpleCharmedAI(Player* player): PlayerAI(player) { } -}; - #endif diff --git a/src/server/game/AI/CreatureAI.h b/src/server/game/AI/CreatureAI.h index 7f266e1615e..f175050e107 100644 --- a/src/server/game/AI/CreatureAI.h +++ b/src/server/game/AI/CreatureAI.h @@ -28,6 +28,7 @@ class WorldObject; class Unit; class Creature; class Player; +class PlayerAI; class SpellInfo; #define TIME_INTERVAL_LOOK 5000 @@ -186,6 +187,11 @@ class TC_GAME_API CreatureAI : public UnitAI virtual bool CanSeeAlways(WorldObject const* /*obj*/) { return false; } + // Called when a player is charmed by the creature + // If a PlayerAI* is returned, that AI is placed on the player instead of the default charm AI + // Object destruction is handled by Unit::RemoveCharmedBy + virtual PlayerAI* GetAIForCharmedPlayer(Player* /*who*/) { return nullptr; } + // intended for encounter design/debugging. do not use for other purposes. expensive. int32 VisualizeBoundary(uint32 duration, Unit* owner=nullptr, bool fill=false) const; virtual bool CheckInRoom(); diff --git a/src/server/game/AI/PlayerAI/PlayerAI.cpp b/src/server/game/AI/PlayerAI/PlayerAI.cpp new file mode 100644 index 00000000000..57f5fd39e6c --- /dev/null +++ b/src/server/game/AI/PlayerAI/PlayerAI.cpp @@ -0,0 +1,184 @@ +/* +* 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, +}; +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->GetSubClass()) & ITEM_SUBCLASS_MASK_WEAPON_RANGED) + { + _isRangedAttacker = true; + break; + } + _isRangedAttacker = false; + break; + } + case CLASS_ROGUE: + _isRangedAttacker = false; + break; + case CLASS_PRIEST: + _isRangedAttacker = me->GetSpecId(me->GetActiveTalentGroup()) == TALENT_SPEC_PRIEST_SHADOW; + break; + case CLASS_DEATH_KNIGHT: + _isRangedAttacker = false; + break; + case CLASS_SHAMAN: + _isRangedAttacker = me->GetSpecId(me->GetActiveTalentGroup()) == TALENT_SPEC_SHAMAN_ELEMENTAL; + break; + case CLASS_MAGE: + _isRangedAttacker = true; + break; + case CLASS_WARLOCK: + _isRangedAttacker = true; + break; + case CLASS_DRUID: + _isRangedAttacker = me->GetSpecId(me->GetActiveTalentGroup()) == TALENT_SPEC_DRUID_BALANCE; + 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->GetSubClass()) + { + 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; + } + } + + 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(); + } +} diff --git a/src/server/game/AI/PlayerAI/PlayerAI.h b/src/server/game/AI/PlayerAI/PlayerAI.h new file mode 100644 index 00000000000..caf0ddd2eec --- /dev/null +++ b/src/server/game/AI/PlayerAI/PlayerAI.h @@ -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 TC_GAME_API 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 TC_GAME_API SimpleCharmedPlayerAI : public PlayerAI +{ + public: + SimpleCharmedPlayerAI(Player* player) : PlayerAI(player) { } + void UpdateAI(uint32 diff) override; + void OnCharmed(bool apply) override; +}; + +#endif diff --git a/src/server/game/Entities/Creature/Creature.h b/src/server/game/Entities/Creature/Creature.h index 00676ad3503..e8e8c8f7a1b 100644 --- a/src/server/game/Entities/Creature/Creature.h +++ b/src/server/game/Entities/Creature/Creature.h @@ -522,7 +522,7 @@ class TC_GAME_API Creature : public Unit, public GridObject<Creature>, public Ma bool AIM_Initialize(CreatureAI* ai = NULL); void Motion_Initialize(); - CreatureAI* AI() const { return (CreatureAI*)i_AI; } + CreatureAI* AI() const { return reinterpret_cast<CreatureAI*>(i_AI); } SpellSchoolMask GetMeleeDamageSchoolMask() const override { return m_meleeDamageSchoolMask; } void SetMeleeDamageSchool(SpellSchools school) { m_meleeDamageSchoolMask = SpellSchoolMask(1 << school); } diff --git a/src/server/game/Entities/Player/Player.cpp b/src/server/game/Entities/Player/Player.cpp index 80570b38659..0d4c8bcda94 100644 --- a/src/server/game/Entities/Player/Player.cpp +++ b/src/server/game/Entities/Player/Player.cpp @@ -1064,10 +1064,8 @@ void Player::Update(uint32 p_time) UpdateAfkReport(now); - if (IsCharmed()) - if (Unit* charmer = GetCharmer()) - if (charmer->GetTypeId() == TYPEID_UNIT && charmer->IsAlive()) - UpdateCharmedAI(); + if (IsAIEnabled && GetAI()) + GetAI()->UpdateAI(p_time); // Update items that have just a limited lifetime if (now > m_Last_tick) @@ -24084,38 +24082,6 @@ bool Player::isTotalImmunity() const return false; } -void Player::UpdateCharmedAI() -{ - //This should only called in Player::Update - Creature* charmer = GetCharmer()->ToCreature(); - - //kill self if charm aura has infinite duration - if (charmer->IsInEvadeMode()) - { - AuraEffectList const& auras = GetAuraEffectsByType(SPELL_AURA_MOD_CHARM); - for (AuraEffectList::const_iterator iter = auras.begin(); iter != auras.end(); ++iter) - if ((*iter)->GetCasterGUID() == charmer->GetGUID() && (*iter)->GetBase()->IsPermanent()) - { - charmer->DealDamage(this, GetHealth(), nullptr, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, nullptr, false); - return; - } - } - - if (!charmer->IsInCombat()) - GetMotionMaster()->MoveFollow(charmer, PET_FOLLOW_DIST, PET_FOLLOW_ANGLE); - - Unit* target = GetVictim(); - if (!target || !charmer->IsValidAttackTarget(target)) - { - target = charmer->SelectNearestTarget(); - if (!target) - return; - - GetMotionMaster()->MoveChase(target); - Attack(target, true); - } -} - uint32 Player::GetRuneTypeBaseCooldown(RuneType runeType) const { float cooldown = RUNE_BASE_COOLDOWN; diff --git a/src/server/game/Entities/Player/Player.h b/src/server/game/Entities/Player/Player.h index 7d19b3a8fb9..68a562d6282 100644 --- a/src/server/game/Entities/Player/Player.h +++ b/src/server/game/Entities/Player/Player.h @@ -54,6 +54,7 @@ class PlayerMenu; class PlayerSocial; class SpellCastTargets; class UpdateMask; +class PlayerAI; typedef std::deque<Mail*> PlayerMails; @@ -1191,6 +1192,8 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> explicit Player(WorldSession* session); ~Player(); + PlayerAI* AI() const { return reinterpret_cast<PlayerAI*>(i_AI); } + void CleanupsBeforeDelete(bool finalCleanup = true) override; void AddToWorld() override; @@ -2782,7 +2785,6 @@ class TC_GAME_API Player : public Unit, public GridObject<Player> MapReference m_mapRef; - void UpdateCharmedAI(); uint32 m_lastFallTime; float m_lastFallZ; diff --git a/src/server/game/Entities/Unit/Unit.cpp b/src/server/game/Entities/Unit/Unit.cpp index e2b01495e4b..ae0b55352a6 100644 --- a/src/server/game/Entities/Unit/Unit.cpp +++ b/src/server/game/Entities/Unit/Unit.cpp @@ -44,6 +44,7 @@ #include "PetAI.h" #include "Pet.h" #include "Player.h" +#include "PlayerAI.h" #include "QuestDef.h" #include "ReputationMgr.h" #include "SpellAuraEffects.h" @@ -14100,11 +14101,21 @@ bool Unit::SetCharmedBy(Unit* charmer, CharmType type, AuraApplication const* au ToCreature()->AI()->OnCharmed(true); GetMotionMaster()->MoveIdle(); } - else + else if (Player* player = ToPlayer()) { - Player* player = ToPlayer(); if (player->isAFK()) player->ToggleAFK(); + + if (Creature* creatureCharmer = charmer->ToCreature()) // we are charmed by a creature + { + IsAIEnabled = true; + i_disabledAI = i_AI; + // set our AI to the charmer's custom charm AI if applicable + if (PlayerAI* charmAI = creatureCharmer->AI()->GetAIForCharmedPlayer(player)) + i_AI = charmAI; + else // otherwise use the default charmed player AI + i_AI = new SimpleCharmedPlayerAI(player); + } player->SetClientControl(this, false); } @@ -14262,7 +14273,24 @@ void Unit::RemoveCharmedBy(Unit* charmer) } if (Player* player = ToPlayer()) + { + if (charmer->GetTypeId() == TYPEID_UNIT) // charmed by a creature, this means we had PlayerAI + { + if (i_AI) + { + // allow charmed player AI to clean up + i_AI->OnCharmed(false); + // then delete it + delete i_AI; + // and restore our previous playerAI (if we had one) + if ((i_AI = i_disabledAI)) + i_disabledAI = nullptr; + else + IsAIEnabled = false; + } + } player->SetClientControl(this, true); + } // a guardian should always have charminfo if (playerCharmer && this != charmer->GetFirstControlled()) diff --git a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp index af85fc95a64..2f82e8b93d7 100644 --- a/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp +++ b/src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp @@ -22,6 +22,7 @@ #include "pit_of_saron.h" #include "Vehicle.h" #include "Player.h" +#include "PlayerAI.h" enum Yells { @@ -438,9 +439,10 @@ class spell_tyrannus_overlord_brand : public SpellScriptLoader if (GetTarget()->GetTypeId() != TYPEID_PLAYER) return; - oldAI = GetTarget()->GetAI(); - oldAIState = GetTarget()->IsAIEnabled; - GetTarget()->SetAI(new player_overlord_brandAI(GetTarget()->ToPlayer(), GetCasterGUID())); + Player* pTarget = GetTarget()->ToPlayer(); + oldAI = pTarget->AI(); + oldAIState = pTarget->IsAIEnabled; + GetTarget()->SetAI(new player_overlord_brandAI(pTarget, GetCasterGUID())); GetTarget()->IsAIEnabled = true; } @@ -450,7 +452,7 @@ class spell_tyrannus_overlord_brand : public SpellScriptLoader return; GetTarget()->IsAIEnabled = oldAIState; - UnitAI* thisAI = GetTarget()->GetAI(); + PlayerAI* thisAI = GetTarget()->ToPlayer()->AI(); GetTarget()->SetAI(oldAI); delete thisAI; } @@ -461,7 +463,7 @@ class spell_tyrannus_overlord_brand : public SpellScriptLoader AfterEffectRemove += AuraEffectRemoveFn(spell_tyrannus_overlord_brand_AuraScript::OnRemove, EFFECT_0, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL); } - UnitAI* oldAI; + PlayerAI* oldAI; bool oldAIState; }; |