aboutsummaryrefslogtreecommitdiff
path: root/src/server/game/Entities/Object
diff options
context:
space:
mode:
authorariel- <ariel-@users.noreply.github.com>2018-02-10 16:43:01 -0300
committerShauren <shauren.trinity@gmail.com>2021-08-28 15:59:11 +0200
commit962f6d7988b9003e550f6745be7cff812e9d8efa (patch)
treeee6ab5872b947afb00f4ca99e87c7dddea35bdb3 /src/server/game/Entities/Object
parent65dca120d34febdaa84a63e17f638ab0fa59b3df (diff)
Core/Spells: rework part 5: GameObject casting
Closes #21330 Closes #18885 Ref #18752 (cherry picked from commit 45c5e1b9d63796d168339a44f63418f220cf2403)
Diffstat (limited to 'src/server/game/Entities/Object')
-rw-r--r--src/server/game/Entities/Object/Object.cpp990
-rw-r--r--src/server/game/Entities/Object/Object.h78
2 files changed, 1062 insertions, 6 deletions
diff --git a/src/server/game/Entities/Object/Object.cpp b/src/server/game/Entities/Object/Object.cpp
index 985d079826c..a2b4067291d 100644
--- a/src/server/game/Entities/Object/Object.cpp
+++ b/src/server/game/Entities/Object/Object.cpp
@@ -21,6 +21,7 @@
#include "BattlefieldMgr.h"
#include "CellImpl.h"
#include "CinematicMgr.h"
+#include "CombatLogPacketsCommon.h"
#include "Common.h"
#include "Creature.h"
#include "GameTime.h"
@@ -36,8 +37,9 @@
#include "OutdoorPvPMgr.h"
#include "PhasingHandler.h"
#include "Player.h"
-#include "SharedDefines.h"
+#include "ReputationMgr.h"
#include "SpellAuraEffects.h"
+#include "SpellMgr.h"
#include "TemporarySummon.h"
#include "Totem.h"
#include "Transport.h"
@@ -848,8 +850,8 @@ void MovementInfo::OutDebug()
TC_LOG_DEBUG("misc", "splineElevation: %f", splineElevation);
}
-WorldObject::WorldObject(bool isWorldObject) : WorldLocation(), LastUsedScriptID(0),
-m_name(""), m_isActive(false), m_isFarVisible(false), m_isWorldObject(isWorldObject), m_zoneScript(nullptr),
+WorldObject::WorldObject(bool isWorldObject) : Object(), WorldLocation(), LastUsedScriptID(0),
+m_movementInfo(), m_name(), m_isActive(false), m_isFarVisible(false), m_isWorldObject(isWorldObject), m_zoneScript(nullptr),
m_transport(nullptr), m_zoneId(0), m_areaId(0), m_staticFloorZ(VMAP_INVALID_HEIGHT), m_currMap(nullptr), m_InstanceId(0),
_dbPhase(0), m_notifyflags(0)
{
@@ -1658,6 +1660,36 @@ void WorldObject::SendMessageToSet(WorldPacket const* data, Player const* skippe
Cell::VisitWorldObjects(this, notifier, GetVisibilityRange());
}
+struct CombatLogSender
+{
+ WorldPackets::CombatLog::CombatLogServerPacket const* i_message;
+
+ explicit CombatLogSender(WorldPackets::CombatLog::CombatLogServerPacket* msg)
+ : i_message(msg)
+ {
+ msg->Write();
+ }
+
+ void operator()(Player const* player) const
+ {
+ if (player->IsAdvancedCombatLoggingEnabled())
+ player->SendDirectMessage(i_message->GetFullLogPacket());
+ else
+ player->SendDirectMessage(i_message->GetBasicLogPacket());
+ }
+};
+
+void WorldObject::SendCombatLogMessage(WorldPackets::CombatLog::CombatLogServerPacket* combatLog) const
+{
+ CombatLogSender combatLogSender(combatLog);
+
+ if (Player const* self = ToPlayer())
+ combatLogSender(self);
+
+ Trinity::MessageDistDeliverer<CombatLogSender> notifier(this, combatLogSender, GetVisibilityRange());
+ Cell::VisitWorldObjects(this, notifier, GetVisibilityRange());
+}
+
void WorldObject::SetMap(Map* map)
{
ASSERT(map);
@@ -1986,6 +2018,958 @@ Player* WorldObject::SelectNearestPlayer(float distance) const
return target;
}
+ObjectGuid WorldObject::GetCharmerOrOwnerOrOwnGUID() const
+{
+ ObjectGuid guid = GetCharmerOrOwnerGUID();
+ if (!guid.IsEmpty())
+ return guid;
+ return GetGUID();
+}
+
+Unit* WorldObject::GetOwner() const
+{
+ return ObjectAccessor::GetUnit(*this, GetOwnerGUID());
+}
+
+Unit* WorldObject::GetCharmerOrOwner() const
+{
+ if (Unit const* unit = ToUnit())
+ return unit->GetCharmerOrOwner();
+ else if (GameObject const* go = ToGameObject())
+ return go->GetOwner();
+
+ return nullptr;
+}
+
+Unit* WorldObject::GetCharmerOrOwnerOrSelf() const
+{
+ if (Unit* u = GetCharmerOrOwner())
+ return u;
+
+ return const_cast<WorldObject*>(this)->ToUnit();
+}
+
+Player* WorldObject::GetCharmerOrOwnerPlayerOrPlayerItself() const
+{
+ ObjectGuid guid = GetCharmerOrOwnerGUID();
+ if (guid.IsPlayer())
+ return ObjectAccessor::GetPlayer(*this, guid);
+
+ return const_cast<WorldObject*>(this)->ToPlayer();
+}
+
+Player* WorldObject::GetAffectingPlayer() const
+{
+ if (!GetCharmerOrOwnerGUID())
+ return const_cast<WorldObject*>(this)->ToPlayer();
+
+ if (Unit* owner = GetCharmerOrOwner())
+ return owner->GetCharmerOrOwnerPlayerOrPlayerItself();
+
+ return nullptr;
+}
+
+Player* WorldObject::GetSpellModOwner() const
+{
+ if (Player* player = const_cast<WorldObject*>(this)->ToPlayer())
+ return player;
+
+ if (GetTypeId() == TYPEID_UNIT)
+ {
+ Creature const* creature = ToCreature();
+ if (creature->IsPet() || creature->IsTotem())
+ {
+ if (Unit* owner = creature->GetOwner())
+ return owner->ToPlayer();
+ }
+ }
+ else if (GetTypeId() == TYPEID_GAMEOBJECT)
+ {
+ GameObject const* go = ToGameObject();
+ if (Unit* owner = go->GetOwner())
+ return owner->ToPlayer();
+ }
+
+ return nullptr;
+}
+
+// function uses real base points (typically value - 1)
+int32 WorldObject::CalculateSpellDamage(Unit const* target, SpellInfo const* spellProto, uint8 effIndex, int32 const* basePoints /*= nullptr*/, float* variance /*= nullptr*/, uint32 castItemId /*= 0*/, int32 itemLevel /*= -1*/) const
+{
+ SpellEffectInfo const* effect = spellProto->GetEffect(effIndex);
+ if (variance)
+ *variance = 0.0f;
+
+ return effect ? effect->CalcValue(this, basePoints, target, variance, castItemId, itemLevel) : 0;
+}
+
+float WorldObject::GetSpellMaxRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const
+{
+ if (!spellInfo->RangeEntry)
+ return 0.f;
+
+ if (spellInfo->RangeEntry->RangeMax[0] == spellInfo->RangeEntry->RangeMax[1])
+ return spellInfo->GetMaxRange();
+
+ if (!target)
+ return spellInfo->GetMaxRange(true);
+
+ return spellInfo->GetMaxRange(!IsHostileTo(target));
+}
+
+float WorldObject::GetSpellMinRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const
+{
+ if (!spellInfo->RangeEntry)
+ return 0.f;
+
+ if (spellInfo->RangeEntry->RangeMin[0] == spellInfo->RangeEntry->RangeMin[1])
+ return spellInfo->GetMinRange();
+
+ if (!target)
+ return spellInfo->GetMinRange(true);
+
+ return spellInfo->GetMinRange(!IsHostileTo(target));
+}
+
+float WorldObject::ApplyEffectModifiers(SpellInfo const* spellInfo, uint8 effIndex, float value) const
+{
+ if (Player* modOwner = GetSpellModOwner())
+ {
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::Points, value);
+ switch (effIndex)
+ {
+ case EFFECT_0:
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::PointsIndex0, value);
+ break;
+ case EFFECT_1:
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::PointsIndex1, value);
+ break;
+ case EFFECT_2:
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::PointsIndex2, value);
+ break;
+ case EFFECT_3:
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::PointsIndex3, value);
+ break;
+ case EFFECT_4:
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::PointsIndex4, value);
+ break;
+ }
+ }
+ return value;
+}
+
+int32 WorldObject::CalcSpellDuration(SpellInfo const* spellInfo) const
+{
+ uint8 comboPoints = 0;
+ if (Unit const* unit = ToUnit())
+ comboPoints = unit->GetPower(POWER_COMBO_POINTS);
+
+ int32 minduration = spellInfo->GetDuration();
+ int32 maxduration = spellInfo->GetMaxDuration();
+
+ int32 duration;
+ if (comboPoints && minduration != -1 && minduration != maxduration)
+ duration = minduration + int32((maxduration - minduration) * comboPoints / 5);
+ else
+ duration = minduration;
+
+ return duration;
+}
+
+int32 WorldObject::ModSpellDuration(SpellInfo const* spellInfo, WorldObject const* target, int32 duration, bool positive, uint32 effectMask) const
+{
+ // don't mod permanent auras duration
+ if (duration < 0)
+ return duration;
+
+ // some auras are not affected by duration modifiers
+ if (spellInfo->HasAttribute(SPELL_ATTR7_IGNORE_DURATION_MODS))
+ return duration;
+
+ // cut duration only of negative effects
+ Unit const* unitTarget = target->ToUnit();
+ if (!unitTarget)
+ return duration;
+
+ if (!positive)
+ {
+ int32 mechanicMask = spellInfo->GetSpellMechanicMaskByEffectMask(effectMask);
+ auto mechanicCheck = [mechanicMask](AuraEffect const* aurEff) -> bool
+ {
+ if (mechanicMask & (1 << aurEff->GetMiscValue()))
+ return true;
+ return false;
+ };
+
+ // Find total mod value (negative bonus)
+ int32 durationMod_always = unitTarget->GetTotalAuraModifier(SPELL_AURA_MECHANIC_DURATION_MOD, mechanicCheck);
+ // Find max mod (negative bonus)
+ int32 durationMod_not_stack = unitTarget->GetMaxNegativeAuraModifier(SPELL_AURA_MECHANIC_DURATION_MOD_NOT_STACK, mechanicCheck);
+
+ // Select strongest negative mod
+ int32 durationMod = std::min(durationMod_always, durationMod_not_stack);
+ if (durationMod != 0)
+ AddPct(duration, durationMod);
+
+ // there are only negative mods currently
+ durationMod_always = unitTarget->GetTotalAuraModifierByMiscValue(SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL, spellInfo->Dispel);
+ durationMod_not_stack = unitTarget->GetMaxNegativeAuraModifierByMiscValue(SPELL_AURA_MOD_AURA_DURATION_BY_DISPEL_NOT_STACK, spellInfo->Dispel);
+
+ durationMod = std::min(durationMod_always, durationMod_not_stack);
+ if (durationMod != 0)
+ AddPct(duration, durationMod);
+ }
+ else
+ {
+ // else positive mods here, there are no currently
+ // when there will be, change GetTotalAuraModifierByMiscValue to GetMaxPositiveAuraModifierByMiscValue
+
+ // Mixology - duration boost
+ if (unitTarget->GetTypeId() == TYPEID_PLAYER)
+ {
+ if (spellInfo->SpellFamilyName == SPELLFAMILY_POTION && (
+ sSpellMgr->IsSpellMemberOfSpellGroup(spellInfo->Id, SPELL_GROUP_ELIXIR_BATTLE) ||
+ sSpellMgr->IsSpellMemberOfSpellGroup(spellInfo->Id, SPELL_GROUP_ELIXIR_GUARDIAN)))
+ {
+ SpellEffectInfo const* effect = spellInfo->GetEffect(EFFECT_0);
+ if (unitTarget->HasAura(53042) && effect && unitTarget->HasSpell(effect->TriggerSpell))
+ duration *= 2;
+ }
+ }
+ }
+
+ return std::max(duration, 0);
+}
+
+void WorldObject::ModSpellCastTime(SpellInfo const* spellInfo, int32& castTime, Spell* spell /*= nullptr*/) const
+{
+ if (!spellInfo || castTime < 0)
+ return;
+
+ // called from caster
+ if (Player* modOwner = GetSpellModOwner())
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::ChangeCastTime, castTime, spell);
+
+ Unit const* unitCaster = ToUnit();
+ if (!unitCaster)
+ return;
+
+ if (!(spellInfo->HasAttribute(SPELL_ATTR0_ABILITY) || spellInfo->HasAttribute(SPELL_ATTR0_TRADESPELL) || spellInfo->HasAttribute(SPELL_ATTR3_NO_DONE_BONUS)) &&
+ ((GetTypeId() == TYPEID_PLAYER && spellInfo->SpellFamilyName) || GetTypeId() == TYPEID_UNIT))
+ castTime = unitCaster->CanInstantCast() ? 0 : int32(float(castTime) * unitCaster->m_unitData->ModCastingSpeed);
+ else if (spellInfo->HasAttribute(SPELL_ATTR0_REQ_AMMO) && !spellInfo->HasAttribute(SPELL_ATTR2_AUTOREPEAT_FLAG))
+ castTime = int32(float(castTime) * unitCaster->m_modAttackSpeedPct[RANGED_ATTACK]);
+ else if (IsPartOfSkillLine(SKILL_COOKING, spellInfo->Id) && unitCaster->HasAura(67556)) // cooking with Chef Hat.
+ castTime = 500;
+}
+
+void WorldObject::ModSpellDurationTime(SpellInfo const* spellInfo, int32& duration, Spell* spell /*= nullptr*/) const
+{
+ if (!spellInfo || duration < 0)
+ return;
+
+ if (spellInfo->IsChanneled() && !spellInfo->HasAttribute(SPELL_ATTR5_HASTE_AFFECT_DURATION))
+ return;
+
+ // called from caster
+ if (Player* modOwner = GetSpellModOwner())
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::ChangeCastTime, duration, spell);
+
+ Unit const* unitCaster = ToUnit();
+ if (!unitCaster)
+ return;
+
+ if (!(spellInfo->HasAttribute(SPELL_ATTR0_ABILITY) || spellInfo->HasAttribute(SPELL_ATTR0_TRADESPELL) || spellInfo->HasAttribute(SPELL_ATTR3_NO_DONE_BONUS)) &&
+ ((GetTypeId() == TYPEID_PLAYER && spellInfo->SpellFamilyName) || GetTypeId() == TYPEID_UNIT))
+ duration = int32(float(duration) * unitCaster->m_unitData->ModCastingSpeed);
+ else if (spellInfo->HasAttribute(SPELL_ATTR0_REQ_AMMO) && !spellInfo->HasAttribute(SPELL_ATTR2_AUTOREPEAT_FLAG))
+ duration = int32(float(duration) * unitCaster->m_modAttackSpeedPct[RANGED_ATTACK]);
+}
+
+float WorldObject::MeleeSpellMissChance(Unit const* /*victim*/, WeaponAttackType /*attType*/, SpellInfo const* /*spellInfo*/) const
+{
+ return 0.0f;
+}
+
+SpellMissInfo WorldObject::MeleeSpellHitResult(Unit* /*victim*/, SpellInfo const* /*spellInfo*/) const
+{
+ return SPELL_MISS_NONE;
+}
+
+SpellMissInfo WorldObject::MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const
+{
+ // Can`t miss on dead target (on skinning for example)
+ if (!victim->IsAlive() && victim->GetTypeId() != TYPEID_PLAYER)
+ return SPELL_MISS_NONE;
+
+ SpellSchoolMask schoolMask = spellInfo->GetSchoolMask();
+ // PvP - PvE spell misschances per leveldif > 2
+ int32 lchance = victim->GetTypeId() == TYPEID_PLAYER ? 7 : 11;
+ int32 thisLevel = GetLevelForTarget(victim);
+ if (GetTypeId() == TYPEID_UNIT && ToCreature()->IsTrigger())
+ thisLevel = std::max<int32>(thisLevel, spellInfo->SpellLevel);
+ int32 leveldif = int32(victim->GetLevelForTarget(this)) - thisLevel;
+ int32 levelBasedHitDiff = leveldif;
+
+ // Base hit chance from attacker and victim levels
+ int32 modHitChance = 100;
+ if (levelBasedHitDiff >= 0)
+ {
+ if (victim->GetTypeId() != TYPEID_PLAYER)
+ {
+ modHitChance = 94 - 3 * std::min(levelBasedHitDiff, 3);
+ levelBasedHitDiff -= 3;
+ }
+ else
+ {
+ modHitChance = 96 - std::min(levelBasedHitDiff, 2);
+ levelBasedHitDiff -= 2;
+ }
+ if (levelBasedHitDiff > 0)
+ modHitChance -= lchance * std::min(levelBasedHitDiff, 7);
+ }
+ else
+ modHitChance = 97 - levelBasedHitDiff;
+
+ // Spellmod from SpellModOp::HitChance
+ if (Player* modOwner = GetSpellModOwner())
+ modOwner->ApplySpellMod(spellInfo, SpellModOp::HitChance, modHitChance);
+
+ // Spells with SPELL_ATTR3_IGNORE_HIT_RESULT will ignore target's avoidance effects
+ if (!spellInfo->HasAttribute(SPELL_ATTR3_IGNORE_HIT_RESULT))
+ {
+ // Chance hit from victim SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE auras
+ modHitChance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_MOD_ATTACKER_SPELL_HIT_CHANCE, schoolMask);
+ }
+
+ int32 HitChance = modHitChance * 100;
+ // Increase hit chance from attacker SPELL_AURA_MOD_SPELL_HIT_CHANCE and attacker ratings
+ if (Unit const* unit = ToUnit())
+ HitChance += int32(unit->m_modSpellHitChance * 100.0f);
+
+ RoundToInterval(HitChance, 0, 10000);
+
+ int32 tmp = 10000 - HitChance;
+
+ int32 rand = irand(0, 9999);
+ if (tmp > 0 && rand < tmp)
+ return SPELL_MISS_MISS;
+
+ // Chance resist mechanic (select max value from every mechanic spell effect)
+ int32 resist_chance = victim->GetMechanicResistChance(spellInfo) * 100;
+
+ // Roll chance
+ if (resist_chance > 0 && rand < (tmp += resist_chance))
+ return SPELL_MISS_RESIST;
+
+ // cast by caster in front of victim
+ if (!victim->HasUnitState(UNIT_STATE_CONTROLLED) && (victim->HasInArc(float(M_PI), this) || victim->HasAuraType(SPELL_AURA_IGNORE_HIT_DIRECTION)))
+ {
+ int32 deflect_chance = victim->GetTotalAuraModifier(SPELL_AURA_DEFLECT_SPELLS) * 100;
+ if (deflect_chance > 0 && rand < (tmp += deflect_chance))
+ return SPELL_MISS_DEFLECT;
+ }
+
+ return SPELL_MISS_NONE;
+}
+
+// Calculate spell hit result can be:
+// Every spell can: Evade/Immune/Reflect/Sucesful hit
+// For melee based spells:
+// Miss
+// Dodge
+// Parry
+// For spells
+// Resist
+SpellMissInfo WorldObject::SpellHitResult(Unit* victim, SpellInfo const* spellInfo, bool canReflect /*= false*/) const
+{
+ // All positive spells can`t miss
+ /// @todo client not show miss log for this spells - so need find info for this in dbc and use it!
+ if (spellInfo->IsPositive() && !IsHostileTo(victim)) // prevent from affecting enemy by "positive" spell
+ return SPELL_MISS_NONE;
+
+ if (this == victim)
+ return SPELL_MISS_NONE;
+
+ // Return evade for units in evade mode
+ if (victim->GetTypeId() == TYPEID_UNIT && victim->ToCreature()->IsEvadingAttacks())
+ return SPELL_MISS_EVADE;
+
+ // Try victim reflect spell
+ if (canReflect)
+ {
+ int32 reflectchance = victim->GetTotalAuraModifier(SPELL_AURA_REFLECT_SPELLS);
+ reflectchance += victim->GetTotalAuraModifierByMiscMask(SPELL_AURA_REFLECT_SPELLS_SCHOOL, spellInfo->GetSchoolMask());
+
+ if (reflectchance > 0 && roll_chance_i(reflectchance))
+ return SPELL_MISS_REFLECT;
+ }
+
+ if (spellInfo->HasAttribute(SPELL_ATTR3_IGNORE_HIT_RESULT))
+ return SPELL_MISS_NONE;
+
+ // Check for immune
+ if (victim->IsImmunedToSpell(spellInfo, this))
+ return SPELL_MISS_IMMUNE;
+
+ // Damage immunity is only checked if the spell has damage effects, this immunity must not prevent aura apply
+ // returns SPELL_MISS_IMMUNE in that case, for other spells, the SMSG_SPELL_GO must show hit
+ if (spellInfo->HasOnlyDamageEffects() && victim->IsImmunedToDamage(spellInfo))
+ return SPELL_MISS_IMMUNE;
+
+ switch (spellInfo->DmgClass)
+ {
+ case SPELL_DAMAGE_CLASS_RANGED:
+ case SPELL_DAMAGE_CLASS_MELEE:
+ return MeleeSpellHitResult(victim, spellInfo);
+ case SPELL_DAMAGE_CLASS_NONE:
+ return SPELL_MISS_NONE;
+ case SPELL_DAMAGE_CLASS_MAGIC:
+ return MagicSpellHitResult(victim, spellInfo);
+ }
+ return SPELL_MISS_NONE;
+}
+
+FactionTemplateEntry const* WorldObject::GetFactionTemplateEntry() const
+{
+ FactionTemplateEntry const* entry = sFactionTemplateStore.LookupEntry(GetFaction());
+ if (!entry)
+ {
+ if (Player const* player = ToPlayer())
+ TC_LOG_ERROR("entities", "Player %s has invalid faction (faction template id) #%u", player->GetName().c_str(), GetFaction());
+ else if (Creature const* creature = ToCreature())
+ TC_LOG_ERROR("entities", "Creature (template id: %u) has invalid faction (faction template id) #%u", creature->GetCreatureTemplate()->Entry, GetFaction());
+ else if (GameObject const* go = ToGameObject())
+ TC_LOG_ERROR("entities", "GameObject (template id: %u) has invalid faction (faction template id) #%u", go->GetGOInfo()->entry, GetFaction());
+ else
+ TC_LOG_ERROR("entities", "WorldObject (name: %s, type: %u) has invalid faction (faction template id) #%u", GetName().c_str(), uint32(GetTypeId()), GetFaction());
+ }
+
+ return entry;
+}
+
+// function based on function Unit::UnitReaction from 13850 client
+ReputationRank WorldObject::GetReactionTo(WorldObject const* target) const
+{
+ // always friendly to self
+ if (this == target)
+ return REP_FRIENDLY;
+
+ // always friendly to charmer or owner
+ if (GetCharmerOrOwnerOrSelf() == target->GetCharmerOrOwnerOrSelf())
+ return REP_FRIENDLY;
+
+ Player const* selfPlayerOwner = GetAffectingPlayer();
+ Player const* targetPlayerOwner = target->GetAffectingPlayer();
+
+ // check forced reputation to support SPELL_AURA_FORCE_REACTION
+ if (selfPlayerOwner)
+ {
+ if (FactionTemplateEntry const* targetFactionTemplateEntry = target->GetFactionTemplateEntry())
+ if (ReputationRank const* repRank = selfPlayerOwner->GetReputationMgr().GetForcedRankIfAny(targetFactionTemplateEntry))
+ return *repRank;
+ }
+ else if (targetPlayerOwner)
+ {
+ if (FactionTemplateEntry const* selfFactionTemplateEntry = GetFactionTemplateEntry())
+ if (ReputationRank const* repRank = targetPlayerOwner->GetReputationMgr().GetForcedRankIfAny(selfFactionTemplateEntry))
+ return *repRank;
+ }
+
+ Unit const* unit = ToUnit();
+ Unit const* targetUnit = target->ToUnit();
+ if (unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ if (targetUnit && targetUnit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ if (selfPlayerOwner && targetPlayerOwner)
+ {
+ // always friendly to other unit controlled by player, or to the player himself
+ if (selfPlayerOwner == targetPlayerOwner)
+ return REP_FRIENDLY;
+
+ // duel - always hostile to opponent
+ if (selfPlayerOwner->duel && selfPlayerOwner->duel->opponent == targetPlayerOwner && selfPlayerOwner->duel->startTime != 0)
+ return REP_HOSTILE;
+
+ // same group - checks dependant only on our faction - skip FFA_PVP for example
+ if (selfPlayerOwner->IsInRaidWith(targetPlayerOwner))
+ return REP_FRIENDLY; // return true to allow config option AllowTwoSide.Interaction.Group to work
+ // however client seems to allow mixed group parties, because in 13850 client it works like:
+ // return GetFactionReactionTo(GetFactionTemplateEntry(), target);
+ }
+
+ // check FFA_PVP
+ if (unit->IsFFAPvP() && targetUnit->IsFFAPvP())
+ return REP_HOSTILE;
+
+ if (selfPlayerOwner)
+ {
+ if (FactionTemplateEntry const* targetFactionTemplateEntry = targetUnit->GetFactionTemplateEntry())
+ {
+ if (ReputationRank const* repRank = selfPlayerOwner->GetReputationMgr().GetForcedRankIfAny(targetFactionTemplateEntry))
+ return *repRank;
+ if (!selfPlayerOwner->HasUnitFlag2(UNIT_FLAG2_IGNORE_REPUTATION))
+ {
+ if (FactionEntry const* targetFactionEntry = sFactionStore.LookupEntry(targetFactionTemplateEntry->Faction))
+ {
+ if (targetFactionEntry->CanHaveReputation())
+ {
+ // check contested flags
+ if (targetFactionTemplateEntry->Flags & FACTION_TEMPLATE_FLAG_CONTESTED_GUARD
+ && selfPlayerOwner->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP))
+ return REP_HOSTILE;
+
+ // if faction has reputation, hostile state depends only from AtWar state
+ if (selfPlayerOwner->GetReputationMgr().IsAtWar(targetFactionEntry))
+ return REP_HOSTILE;
+ return REP_FRIENDLY;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ // do checks dependant only on our faction
+ return WorldObject::GetFactionReactionTo(GetFactionTemplateEntry(), target);
+}
+
+/*static*/ ReputationRank WorldObject::GetFactionReactionTo(FactionTemplateEntry const* factionTemplateEntry, WorldObject const* target)
+{
+ // always neutral when no template entry found
+ if (!factionTemplateEntry)
+ return REP_NEUTRAL;
+
+ FactionTemplateEntry const* targetFactionTemplateEntry = target->GetFactionTemplateEntry();
+ if (!targetFactionTemplateEntry)
+ return REP_NEUTRAL;
+
+ if (Player const* targetPlayerOwner = target->GetAffectingPlayer())
+ {
+ // check contested flags
+ if (factionTemplateEntry->Flags & FACTION_TEMPLATE_FLAG_CONTESTED_GUARD
+ && targetPlayerOwner->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP))
+ return REP_HOSTILE;
+ if (ReputationRank const* repRank = targetPlayerOwner->GetReputationMgr().GetForcedRankIfAny(factionTemplateEntry))
+ return *repRank;
+ if (target->IsUnit() && !target->ToUnit()->HasUnitFlag2(UNIT_FLAG2_IGNORE_REPUTATION))
+ {
+ if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplateEntry->Faction))
+ {
+ if (factionEntry->CanHaveReputation())
+ {
+ // CvP case - check reputation, don't allow state higher than neutral when at war
+ ReputationRank repRank = targetPlayerOwner->GetReputationMgr().GetRank(factionEntry);
+ if (targetPlayerOwner->GetReputationMgr().IsAtWar(factionEntry))
+ repRank = std::min(REP_NEUTRAL, repRank);
+ return repRank;
+ }
+ }
+ }
+ }
+
+ // common faction based check
+ if (factionTemplateEntry->IsHostileTo(targetFactionTemplateEntry))
+ return REP_HOSTILE;
+ if (factionTemplateEntry->IsFriendlyTo(targetFactionTemplateEntry))
+ return REP_FRIENDLY;
+ if (targetFactionTemplateEntry->IsFriendlyTo(factionTemplateEntry))
+ return REP_FRIENDLY;
+ if (factionTemplateEntry->Flags & FACTION_TEMPLATE_FLAG_HOSTILE_BY_DEFAULT)
+ return REP_HOSTILE;
+ // neutral by default
+ return REP_NEUTRAL;
+}
+
+bool WorldObject::IsHostileTo(WorldObject const* target) const
+{
+ return GetReactionTo(target) <= REP_HOSTILE;
+}
+
+bool WorldObject::IsFriendlyTo(WorldObject const* target) const
+{
+ return GetReactionTo(target) >= REP_FRIENDLY;
+}
+
+bool WorldObject::IsHostileToPlayers() const
+{
+ FactionTemplateEntry const* my_faction = GetFactionTemplateEntry();
+ if (!my_faction->Faction)
+ return false;
+
+ FactionEntry const* raw_faction = sFactionStore.LookupEntry(my_faction->Faction);
+ if (raw_faction && raw_faction->ReputationIndex >= 0)
+ return false;
+
+ return my_faction->IsHostileToPlayers();
+}
+
+bool WorldObject::IsNeutralToAll() const
+{
+ FactionTemplateEntry const* my_faction = GetFactionTemplateEntry();
+ if (!my_faction->Faction)
+ return true;
+
+ FactionEntry const* raw_faction = sFactionStore.LookupEntry(my_faction->Faction);
+ if (raw_faction && raw_faction->ReputationIndex >= 0)
+ return false;
+
+ return my_faction->IsNeutralToAll();
+}
+
+void WorldObject::CastSpell(SpellCastTargets const& targets, uint32 spellId, CastSpellExtraArgs const& args /*= { }*/)
+{
+ SpellInfo const* info = sSpellMgr->GetSpellInfo(spellId, args.CastDifficulty != DIFFICULTY_NONE ? args.CastDifficulty : GetMap()->GetDifficultyID());
+ if (!info)
+ {
+ TC_LOG_ERROR("entities.unit", "CastSpell: unknown spell %u by caster %s", spellId, GetGUID().ToString().c_str());
+ return;
+ }
+
+ Spell* spell = new Spell(this, info, args.TriggerFlags, args.OriginalCaster);
+ for (auto const& pair : args.SpellValueOverrides)
+ spell->SetSpellValue(pair.first, pair.second);
+
+ spell->m_CastItem = args.CastItem;
+ spell->prepare(targets, args.TriggeringAura);
+}
+
+void WorldObject::CastSpell(WorldObject* target, uint32 spellId, CastSpellExtraArgs const& args /*= { }*/)
+{
+ SpellCastTargets targets;
+ if (target)
+ {
+ if (Unit* unitTarget = target->ToUnit())
+ targets.SetUnitTarget(unitTarget);
+ else if (GameObject* goTarget = target->ToGameObject())
+ targets.SetGOTarget(goTarget);
+ else
+ {
+ TC_LOG_ERROR("entities.unit", "CastSpell: Invalid target %s passed to spell cast by %s", target->GetGUID().ToString().c_str(), GetGUID().ToString().c_str());
+ return;
+ }
+ }
+ CastSpell(targets, spellId, args);
+}
+
+void WorldObject::CastSpell(Position const& dest, uint32 spellId, CastSpellExtraArgs const& args /*= { }*/)
+{
+ SpellCastTargets targets;
+ targets.SetDst(dest);
+ CastSpell(targets, spellId, args);
+}
+
+// function based on function Unit::CanAttack from 13850 client
+bool WorldObject::IsValidAttackTarget(WorldObject const* target, SpellInfo const* bySpell /*= nullptr*/, bool spellCheck /*= true*/) const
+{
+ ASSERT(target);
+
+ // can't attack self
+ if (this == target)
+ return false;
+
+ // can't attack GMs
+ if (target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->IsGameMaster())
+ return false;
+
+ Unit const* unit = ToUnit();
+ Unit const* targetUnit = target->ToUnit();
+
+ // CvC case - can attack each other only when one of them is hostile
+ if (unit && !unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && targetUnit && !targetUnit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ return IsHostileTo(target) || target->IsHostileTo(this);
+
+ // PvP, PvC, CvP case
+ // can't attack friendly targets
+ if (IsFriendlyTo(target) || target->IsFriendlyTo(this))
+ return false;
+
+ Player const* playerAffectingAttacker = unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) ? GetAffectingPlayer() : nullptr;
+ Player const* playerAffectingTarget = targetUnit && targetUnit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) ? target->GetAffectingPlayer() : nullptr;
+
+ // Not all neutral creatures can be attacked (even some unfriendly faction does not react aggresive to you, like Sporaggar)
+ if ((playerAffectingAttacker && !playerAffectingTarget) || (!playerAffectingAttacker && playerAffectingTarget))
+ {
+ Player const* player = playerAffectingAttacker ? playerAffectingAttacker : playerAffectingTarget;
+
+ if (Unit const* creature = playerAffectingAttacker ? targetUnit : unit)
+ {
+ if (creature->IsContestedGuard() && player->HasPlayerFlag(PLAYER_FLAGS_CONTESTED_PVP))
+ return true;
+
+ if (FactionTemplateEntry const* factionTemplate = creature->GetFactionTemplateEntry())
+ {
+ if (!(player->GetReputationMgr().GetForcedRankIfAny(factionTemplate)))
+ if (FactionEntry const* factionEntry = sFactionStore.LookupEntry(factionTemplate->Faction))
+ if (FactionState const* repState = player->GetReputationMgr().GetState(factionEntry))
+ if (!repState->Flags.HasFlag(ReputationFlags::AtWar))
+ return false;
+
+ }
+ }
+ }
+
+ Creature const* creatureAttacker = ToCreature();
+ if (creatureAttacker && (creatureAttacker->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT))
+ return false;
+
+ if (!bySpell)
+ spellCheck = false;
+
+ if (spellCheck && !IsValidSpellAttackTarget(target, bySpell))
+ return false;
+
+ return true;
+}
+
+bool WorldObject::IsValidSpellAttackTarget(WorldObject const* target, SpellInfo const* bySpell) const
+{
+ ASSERT(target);
+ ASSERT(bySpell);
+
+ // can't attack unattackable units
+ Unit const* unitTarget = target->ToUnit();
+ if (unitTarget && unitTarget->HasUnitState(UNIT_STATE_UNATTACKABLE))
+ return false;
+
+ Unit const* unit = ToUnit();
+ // visibility checks (only units)
+ if (unit)
+ {
+ // can't attack invisible
+ if (!bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_INVISIBLE))
+ {
+ if (!unit->CanSeeOrDetect(target, bySpell->IsAffectingArea()))
+ return false;
+
+ /*
+ else if (!obj)
+ {
+ // ignore stealth for aoe spells. Ignore stealth if target is player and unit in combat with same player
+ bool const ignoreStealthCheck = (bySpell && bySpell->IsAffectingArea()) ||
+ (target->GetTypeId() == TYPEID_PLAYER && target->HasStealthAura() && IsInCombatWith(target));
+
+ if (!CanSeeOrDetect(target, ignoreStealthCheck))
+ return false;
+ }
+ */
+ }
+ }
+
+ // can't attack dead
+ if (!bySpell->IsAllowingDeadTarget() && unitTarget && !unitTarget->IsAlive())
+ return false;
+
+ // can't attack untargetable
+ if (!bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE) && unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
+ return false;
+
+ if (Player const* playerAttacker = ToPlayer())
+ {
+ if (playerAttacker->HasPlayerFlag(PLAYER_FLAGS_UBER))
+ return false;
+ }
+
+ // check flags
+ if (unitTarget && unitTarget->HasUnitFlag(UnitFlags(UNIT_FLAG_NON_ATTACKABLE | UNIT_FLAG_TAXI_FLIGHT | UNIT_FLAG_NOT_ATTACKABLE_1 | UNIT_FLAG_UNK_16)))
+ return false;
+
+ if (unit && !unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && unitTarget && unitTarget->IsImmuneToNPC())
+ return false;
+
+ if (unitTarget && !unitTarget->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && unit && unit->IsImmuneToNPC())
+ return false;
+
+ if (!bySpell->HasAttribute(SPELL_ATTR8_ATTACK_IGNORE_IMMUNE_TO_PC_FLAG))
+ {
+ if (unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && unitTarget && unitTarget->IsImmuneToPC())
+ return false;
+
+ if (unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && unit && unit->IsImmuneToPC())
+ return false;
+ }
+
+ // check duel - before sanctuary checks
+ Player const* playerAffectingAttacker = unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) ? GetAffectingPlayer() : nullptr;
+ Player const* playerAffectingTarget = unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) ? target->GetAffectingPlayer() : nullptr;
+ if (playerAffectingAttacker && playerAffectingTarget)
+ if (playerAffectingAttacker->duel && playerAffectingAttacker->duel->opponent == playerAffectingTarget && playerAffectingAttacker->duel->startTime != 0)
+ return true;
+
+ // PvP case - can't attack when attacker or target are in sanctuary
+ // however, 13850 client doesn't allow to attack when one of the unit's has sanctuary flag and is pvp
+ if (unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE) && (unitTarget->IsInSanctuary() || unit->IsInSanctuary()))
+ return false;
+
+ // additional checks - only PvP case
+ if (playerAffectingAttacker && playerAffectingTarget)
+ {
+ if (unitTarget->IsPvP())
+ return true;
+
+ if (unit->IsFFAPvP() && unitTarget->IsFFAPvP())
+ return true;
+
+ return unit->HasPvpFlag(UNIT_BYTE2_FLAG_UNK1) ||
+ unitTarget->HasPvpFlag(UNIT_BYTE2_FLAG_UNK1);
+ }
+
+ return true;
+}
+
+// function based on function Unit::CanAssist from 13850 client
+bool WorldObject::IsValidAssistTarget(WorldObject const* target, SpellInfo const* bySpell /*= nullptr*/, bool spellCheck /*= true*/) const
+{
+ ASSERT(target);
+
+ // can assist to self
+ if (this == target)
+ return true;
+
+ // can't assist GMs
+ if (target->GetTypeId() == TYPEID_PLAYER && target->ToPlayer()->IsGameMaster())
+ return false;
+
+ // can't assist non-friendly targets
+ if (GetReactionTo(target) < REP_NEUTRAL && target->GetReactionTo(this) < REP_NEUTRAL && (!ToCreature() || !(ToCreature()->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT)))
+ return false;
+
+ if (!bySpell)
+ spellCheck = false;
+
+ if (spellCheck && !IsValidSpellAssistTarget(target, bySpell))
+ return false;
+
+ return true;
+}
+
+bool WorldObject::IsValidSpellAssistTarget(WorldObject const* target, SpellInfo const* bySpell) const
+{
+ ASSERT(target);
+ ASSERT(bySpell);
+
+ // can't assist unattackable units
+ Unit const* unitTarget = target->ToUnit();
+ if (unitTarget && unitTarget->HasUnitState(UNIT_STATE_UNATTACKABLE))
+ return false;
+
+ // can't assist own vehicle or passenger
+ Unit const* unit = ToUnit();
+ if (unit && unitTarget && unit->GetVehicle())
+ {
+ if (unit->IsOnVehicle(unitTarget))
+ return false;
+
+ if (unit->GetVehicleBase()->IsOnVehicle(unitTarget))
+ return false;
+ }
+
+ // can't assist invisible
+ if (!bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_INVISIBLE) && !CanSeeOrDetect(target, bySpell->IsAffectingArea()))
+ return false;
+
+ // can't assist dead
+ if (!bySpell->IsAllowingDeadTarget() && unitTarget && !unitTarget->IsAlive())
+ return false;
+
+ // can't assist untargetable
+ if (!bySpell->HasAttribute(SPELL_ATTR6_CAN_TARGET_UNTARGETABLE) && unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_NOT_SELECTABLE))
+ return false;
+
+ if (!bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG))
+ {
+ if (unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ if (unitTarget && unitTarget->IsImmuneToPC())
+ return false;
+ }
+ else
+ {
+ if (unitTarget && unitTarget->IsImmuneToNPC())
+ return false;
+ }
+ }
+
+ // PvP case
+ if (unitTarget && unitTarget->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ Player const* targetPlayerOwner = target->GetAffectingPlayer();
+ if (unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ Player const* selfPlayerOwner = GetAffectingPlayer();
+ if (selfPlayerOwner && targetPlayerOwner)
+ {
+ // can't assist player which is dueling someone
+ if (selfPlayerOwner != targetPlayerOwner && targetPlayerOwner->duel)
+ return false;
+ }
+ // can't assist player in ffa_pvp zone from outside
+ if (unitTarget->IsFFAPvP() && unit && !unit->IsFFAPvP())
+ return false;
+
+ // can't assist player out of sanctuary from sanctuary if has pvp enabled
+ if (unitTarget->IsPvP())
+ if (unit && unit->IsInSanctuary() && !unitTarget->IsInSanctuary())
+ return false;
+ }
+ }
+ // PvC case - player can assist creature only if has specific type flags
+ // !target->HasFlag(UNIT_FIELD_FLAGS, UNIT_FLAG_PVP_ATTACKABLE) &&
+ else if (unit && unit->HasUnitFlag(UNIT_FLAG_PVP_ATTACKABLE))
+ {
+ if (!bySpell->HasAttribute(SPELL_ATTR6_ASSIST_IGNORE_IMMUNE_FLAG))
+ if (unitTarget && !unitTarget->IsPvP())
+ if (Creature const* creatureTarget = target->ToCreature())
+ return ((creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_TREAT_AS_RAID_UNIT) || (creatureTarget->GetCreatureTemplate()->type_flags & CREATURE_TYPE_FLAG_CAN_ASSIST));
+ }
+
+ return true;
+}
+
+Unit* WorldObject::GetMagicHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo)
+{
+ // Patch 1.2 notes: Spell Reflection no longer reflects abilities
+ if (spellInfo->HasAttribute(SPELL_ATTR0_ABILITY) || spellInfo->HasAttribute(SPELL_ATTR1_CANT_BE_REDIRECTED) || spellInfo->HasAttribute(SPELL_ATTR0_UNAFFECTED_BY_INVULNERABILITY))
+ return victim;
+
+ Unit::AuraEffectList const& magnetAuras = victim->GetAuraEffectsByType(SPELL_AURA_SPELL_MAGNET);
+ for (AuraEffect const* aurEff : magnetAuras)
+ {
+ if (Unit* magnet = aurEff->GetBase()->GetCaster())
+ {
+ if (spellInfo->CheckExplicitTarget(this, magnet) == SPELL_CAST_OK && IsValidAttackTarget(magnet, spellInfo))
+ {
+ /// @todo handle this charge drop by proc in cast phase on explicit target
+ if (spellInfo->HasHitDelay())
+ {
+ // Set up missile speed based delay
+ float hitDelay = spellInfo->LaunchDelay;
+ if (spellInfo->HasAttribute(SPELL_ATTR9_SPECIAL_DELAY_CALCULATION))
+ hitDelay += spellInfo->Speed;
+ else if (spellInfo->Speed > 0.0f)
+ hitDelay += std::max(victim->GetDistance(this), 5.0f) / spellInfo->Speed;
+
+ uint32 delay = uint32(std::floor(hitDelay * 1000.0f));
+ // Schedule charge drop
+ aurEff->GetBase()->DropChargeDelayed(delay, AURA_REMOVE_BY_EXPIRE);
+ }
+ else
+ aurEff->GetBase()->DropCharge(AURA_REMOVE_BY_EXPIRE);
+
+ return magnet;
+ }
+ }
+ }
+ return victim;
+}
+
+uint32 WorldObject::GetCastSpellXSpellVisualId(SpellInfo const* spellInfo) const
+{
+ return spellInfo->GetSpellXSpellVisualId(this);
+}
+
template <typename Container>
void WorldObject::GetGameObjectListWithEntryInGrid(Container& gameObjectContainer, uint32 entry, float maxSearchRange /*= 250.0f*/) const
{
diff --git a/src/server/game/Entities/Object/Object.h b/src/server/game/Entities/Object/Object.h
index 5f46af426d8..4e0d9005b07 100644
--- a/src/server/game/Entities/Object/Object.h
+++ b/src/server/game/Entities/Object/Object.h
@@ -20,6 +20,7 @@
#include "Common.h"
#include "Duration.h"
+#include "EventProcessor.h"
#include "GridReference.h"
#include "GridRefManager.h"
#include "ModelIgnoreFlags.h"
@@ -47,6 +48,9 @@ class Map;
class Object;
class Player;
class Scenario;
+class Spell;
+class SpellCastTargets;
+class SpellInfo;
class TempSummon;
class Transport;
class Unit;
@@ -54,9 +58,18 @@ class UpdateData;
class WorldObject;
class WorldPacket;
class ZoneScript;
+struct FactionTemplateEntry;
struct PositionFullTerrainStatus;
struct QuaternionData;
+namespace WorldPackets
+{
+ namespace CombatLog
+ {
+ class CombatLogServerPacket;
+ }
+}
+
typedef std::unordered_map<Player*, UpdateData> UpdateDataMapType;
struct CreateObjectBits
@@ -400,7 +413,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
public:
virtual ~WorldObject();
- virtual void Update (uint32 /*time_diff*/) { }
+ virtual void Update(uint32 /*time_diff*/) { }
void AddToWorld() override;
void RemoveFromWorld() override;
@@ -491,6 +504,8 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
virtual void SendMessageToSetInRange(WorldPacket const* data, float dist, bool self) const;
virtual void SendMessageToSet(WorldPacket const* data, Player const* skipped_rcvr) const;
+ void SendCombatLogMessage(WorldPackets::CombatLog::CombatLogServerPacket* combatLog) const;
+
virtual uint8 GetLevelForTarget(WorldObject const* /*target*/) const { return 1; }
void PlayDistanceSound(uint32 soundId, Player* target = nullptr);
@@ -538,6 +553,61 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
GameObject* FindNearestGameObjectOfType(GameobjectTypes type, float range) const;
Player* SelectNearestPlayer(float distance) const;
+ virtual ObjectGuid GetOwnerGUID() const = 0;
+ virtual ObjectGuid GetCharmerOrOwnerGUID() const { return GetOwnerGUID(); }
+ ObjectGuid GetCharmerOrOwnerOrOwnGUID() const;
+
+ Unit* GetOwner() const;
+ Unit* GetCharmerOrOwner() const;
+ Unit* GetCharmerOrOwnerOrSelf() const;
+ Player* GetCharmerOrOwnerPlayerOrPlayerItself() const;
+ Player* GetAffectingPlayer() const;
+
+ Player* GetSpellModOwner() const;
+ int32 CalculateSpellDamage(Unit const* target, SpellInfo const* spellInfo, uint8 effIndex, int32 const* basePoints = nullptr, float* variance = nullptr, uint32 castItemId = 0, int32 itemLevel = -1) const;
+
+ // target dependent range checks
+ float GetSpellMaxRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const;
+ float GetSpellMinRangeForTarget(Unit const* target, SpellInfo const* spellInfo) const;
+
+ float ApplyEffectModifiers(SpellInfo const* spellInfo, uint8 effIndex, float value) const;
+ int32 CalcSpellDuration(SpellInfo const* spellInfo) const;
+ int32 ModSpellDuration(SpellInfo const* spellInfo, WorldObject const* target, int32 duration, bool positive, uint32 effectMask) const;
+ void ModSpellCastTime(SpellInfo const* spellInfo, int32& castTime, Spell* spell = nullptr) const;
+ void ModSpellDurationTime(SpellInfo const* spellInfo, int32& durationTime, Spell* spell = nullptr) const;
+
+ virtual float MeleeSpellMissChance(Unit const* victim, WeaponAttackType attType, SpellInfo const* spellInfo) const;
+ virtual SpellMissInfo MeleeSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const;
+ SpellMissInfo MagicSpellHitResult(Unit* victim, SpellInfo const* spellInfo) const;
+ SpellMissInfo SpellHitResult(Unit* victim, SpellInfo const* spellInfo, bool canReflect = false) const;
+
+ virtual uint32 GetFaction() const = 0;
+ virtual void SetFaction(uint32 /*faction*/) { }
+ FactionTemplateEntry const* GetFactionTemplateEntry() const;
+
+ ReputationRank GetReactionTo(WorldObject const* target) const;
+ static ReputationRank GetFactionReactionTo(FactionTemplateEntry const* factionTemplateEntry, WorldObject const* target);
+
+ bool IsHostileTo(WorldObject const* target) const;
+ bool IsHostileToPlayers() const;
+ bool IsFriendlyTo(WorldObject const* target) const;
+ bool IsNeutralToAll() const;
+
+ // CastSpell's third arg can be a variety of things - check out CastSpellExtraArgs' constructors!
+ void CastSpell(SpellCastTargets const& targets, uint32 spellId, CastSpellExtraArgs const& args = { });
+ void CastSpell(WorldObject* target, uint32 spellId, CastSpellExtraArgs const& args = { });
+ void CastSpell(Position const& dest, uint32 spellId, CastSpellExtraArgs const& args = { });
+
+ bool IsValidAttackTarget(WorldObject const* target, SpellInfo const* bySpell = nullptr, bool spellCheck = true) const;
+ bool IsValidSpellAttackTarget(WorldObject const* target, SpellInfo const* bySpell) const;
+
+ bool IsValidAssistTarget(WorldObject const* target, SpellInfo const* bySpell = nullptr, bool spellCheck = true) const;
+ bool IsValidSpellAssistTarget(WorldObject const* target, SpellInfo const* bySpell) const;
+
+ Unit* GetMagicHitRedirectTarget(Unit* victim, SpellInfo const* spellInfo);
+
+ virtual uint32 GetCastSpellXSpellVisualId(SpellInfo const* spellInfo) const;
+
template <typename Container>
void GetGameObjectListWithEntryInGrid(Container& gameObjectContainer, uint32 entry, float maxSearchRange = 250.0f) const;
@@ -599,6 +669,9 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
float GetMapWaterOrGroundLevel(float x, float y, float z, float* ground = nullptr) const;
float GetMapHeight(float x, float y, float z, bool vmap = true, float distanceToSearch = 50.0f) const; // DEFAULT_HEIGHT_SEARCH in map.h
+ // Event handler
+ EventProcessor m_Events;
+
virtual uint16 GetAIAnimKitId() const { return 0; }
virtual uint16 GetMovementAnimKitId() const { return 0; }
virtual uint16 GetMeleeAnimKitId() const { return 0; }
@@ -614,7 +687,7 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
bool m_isActive;
bool m_isFarVisible;
Optional<float> m_visibilityDistanceOverride;
- const bool m_isWorldObject;
+ bool const m_isWorldObject;
ZoneScript* m_zoneScript;
// transports
@@ -640,7 +713,6 @@ class TC_GAME_API WorldObject : public Object, public WorldLocation
private:
Map* m_currMap; // current object's Map location
- //uint32 m_mapId; // object at map with map_id
uint32 m_InstanceId; // in map copy with instance id
PhaseShift _phaseShift;
PhaseShift _suppressedPhaseShift; // contains phases for current area but not applied due to conditions