diff options
| author | ariel- <ariel-@users.noreply.github.com> | 2018-02-10 16:43:01 -0300 |
|---|---|---|
| committer | Shauren <shauren.trinity@gmail.com> | 2021-08-28 15:59:11 +0200 |
| commit | 962f6d7988b9003e550f6745be7cff812e9d8efa (patch) | |
| tree | ee6ab5872b947afb00f4ca99e87c7dddea35bdb3 /src/server/game/Entities/Object | |
| parent | 65dca120d34febdaa84a63e17f638ab0fa59b3df (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.cpp | 990 | ||||
| -rw-r--r-- | src/server/game/Entities/Object/Object.h | 78 |
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 |
