Merge pull request #16644 from Treeston/3.3.5-customcharmai

Core/UnitAI: Rework creature-controlled player behavior.
This commit is contained in:
Treeston committed 2016-02-24 13:39:31 +01:00
commit 4e4b2b9a13
13 files changed
+304 -102

No files matched your search

-30
View File
@@ -259,36 +259,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->GetSpellForDifficultyFromSpell(sSpellMgr->GetSpellInfo(spellId), caster))
{
-17
View File
@@ -268,21 +268,4 @@ class UnitAI
UnitAI& operator=(UnitAI const& right) = delete;
};
class PlayerAI : public UnitAI
{
protected:
Player* const me;
public:
explicit PlayerAI(Player* player) : UnitAI((Unit*)player), me(player) { }
void OnCharmed(bool apply) override;
};
class SimpleCharmedAI : public PlayerAI
{
public:
void UpdateAI(uint32 diff) override;
SimpleCharmedAI(Player* player): PlayerAI(player) { }
};
#endif
+6
View File
@@ -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 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();
+195
View File
@@ -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();
}
}
+52
View File
@@ -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
+1
View File
@@ -108,6 +108,7 @@ include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/Addons
${CMAKE_CURRENT_SOURCE_DIR}/AI
${CMAKE_CURRENT_SOURCE_DIR}/AI/CoreAI
${CMAKE_CURRENT_SOURCE_DIR}/AI/PlayerAI
${CMAKE_CURRENT_SOURCE_DIR}/AI/ScriptedAI
${CMAKE_CURRENT_SOURCE_DIR}/AI/SmartScripts
${CMAKE_CURRENT_SOURCE_DIR}/AuctionHouse
+1 -1
View File
@@ -479,7 +479,7 @@ class Creature : public Unit, public GridObject<Creature>, public MapObject
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); }
bool SetWalk(bool enable) override;
bool SetDisableGravity(bool disable, bool packetOnly = false) override;
+1 -38
View File
@@ -1237,12 +1237,7 @@ void Player::Update(uint32 p_time)
UpdateAfkReport(now);
if (IsCharmed())
if (Unit* charmer = GetCharmer())
if (charmer->GetTypeId() == TYPEID_UNIT && charmer->IsAlive())
UpdateCharmedAI();
if (GetAI() && IsAIEnabled)
if (IsAIEnabled && GetAI())
GetAI()->UpdateAI(p_time);
// Update items that have just a limited lifetime
@@ -24129,38 +24124,6 @@ bool Player::isTotalImmunity()
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(), NULL, DIRECT_DAMAGE, SPELL_SCHOOL_MASK_NORMAL, NULL, 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::GetRuneBaseCooldown(uint8 index)
{
uint8 rune = GetBaseRune(index);
+3 -2
View File
@@ -54,6 +54,7 @@ class PlayerMenu;
class PlayerSocial;
class SpellCastTargets;
class UpdateMask;
class PlayerAI;
struct CharacterCustomizeInfo;
@@ -1022,6 +1023,8 @@ class 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;
@@ -2533,8 +2536,6 @@ class Player : public Unit, public GridObject<Player>
MapReference m_mapRef;
void UpdateCharmedAI();
uint32 m_lastFallTime;
float m_lastFallZ;
+30 -2
View File
@@ -45,6 +45,7 @@
#include "PetAI.h"
#include "Pet.h"
#include "Player.h"
#include "PlayerAI.h"
#include "QuestDef.h"
#include "ReputationMgr.h"
#include "SpellAuraEffects.h"
@@ -15902,11 +15903,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);
}
@@ -16064,7 +16075,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())
+1
View File
@@ -73,6 +73,7 @@ include_directories(
${CMAKE_SOURCE_DIR}/src/server/game/Addons
${CMAKE_SOURCE_DIR}/src/server/game/AI
${CMAKE_SOURCE_DIR}/src/server/game/AI/CoreAI
${CMAKE_SOURCE_DIR}/src/server/game/AI/PlayerAI
${CMAKE_SOURCE_DIR}/src/server/game/AI/ScriptedAI
${CMAKE_SOURCE_DIR}/src/server/game/AI/SmartScripts
${CMAKE_SOURCE_DIR}/src/server/game/AuctionHouse
@@ -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(static_cast<UnitAI*>(new player_overlord_brandAI(pTarget, GetCasterGUID())));
GetTarget()->IsAIEnabled = true;
}
@@ -450,8 +452,8 @@ class spell_tyrannus_overlord_brand : public SpellScriptLoader
return;
GetTarget()->IsAIEnabled = oldAIState;
UnitAI* thisAI = GetTarget()->GetAI();
GetTarget()->SetAI(oldAI);
PlayerAI* thisAI = GetTarget()->ToPlayer()->AI();
GetTarget()->SetAI(static_cast<UnitAI*>(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;
};
+6 -6
View File
@@ -22,7 +22,7 @@
*/
#include "Player.h"
#include "UnitAI.h"
#include "PlayerAI.h"
#include "ScriptMgr.h"
#include "SpellScript.h"
#include "SpellAuraEffects.h"
@@ -1909,9 +1909,9 @@ public:
if (!player || player->GetGhoulResurrectGhoulGUID().IsEmpty())
return;
oldAI = player->GetAI();
oldAI = player->AI();
oldAIState = player->IsAIEnabled;
player->SetAI(new player_ghoulAI(player, player->GetGhoulResurrectGhoulGUID()));
player->SetAI(static_cast<UnitAI*>(new player_ghoulAI(player, player->GetGhoulResurrectGhoulGUID())));
player->IsAIEnabled = true;
}
@@ -1922,8 +1922,8 @@ public:
return;
player->IsAIEnabled = oldAIState;
UnitAI* thisAI = player->GetAI();
player->SetAI(oldAI);
PlayerAI* thisAI = player->AI();
player->SetAI(static_cast<UnitAI*>(oldAI));
delete thisAI;
// Dismiss ghoul if necessary
@@ -1943,7 +1943,7 @@ public:
AfterEffectRemove += AuraEffectRemoveFn(spell_dk_raise_ally_AuraScript::OnRemove, EFFECT_1, SPELL_AURA_DUMMY, AURA_EFFECT_HANDLE_REAL);
}
UnitAI* oldAI;
PlayerAI* oldAI;
bool oldAIState;
};