aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/server/game/AI/CoreAI/UnitAI.cpp30
-rw-r--r--src/server/game/AI/CoreAI/UnitAI.h17
-rw-r--r--src/server/game/AI/CreatureAI.h6
-rw-r--r--src/server/game/AI/PlayerAI/PlayerAI.cpp184
-rw-r--r--src/server/game/AI/PlayerAI/PlayerAI.h52
-rw-r--r--src/server/game/Entities/Creature/Creature.h2
-rw-r--r--src/server/game/Entities/Player/Player.cpp38
-rw-r--r--src/server/game/Entities/Player/Player.h4
-rw-r--r--src/server/game/Entities/Unit/Unit.cpp32
-rw-r--r--src/server/scripts/Northrend/FrozenHalls/PitOfSaron/boss_scourgelord_tyrannus.cpp12
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;
};